From fa8eba27c9a6a2da5e4c10585df0aeed4470ae20 Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Thu, 4 Jul 2024 15:27:37 +0000 Subject: [PATCH 1/7] 8307383: Enhance DTLS connections Reviewed-by: mbaesken, andrew Backport-of: 362dbbaa952b3d4a5270c6bfae879a12e9bdf4d1 --- .../classes/sun/security/ssl/ClientHello.java | 5 +- .../sun/security/ssl/DTLSInputRecord.java | 137 +++++++++++++++++- .../security/ssl/ServerHandshakeContext.java | 3 +- .../sun/security/ssl/TransportContext.java | 7 +- .../javax/net/ssl/DTLS/InvalidRecords.java | 32 +++- .../jdk/javax/net/ssl/TLSCommon/MFLNTest.java | 12 +- 6 files changed, 184 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/ClientHello.java b/src/java.base/share/classes/sun/security/ssl/ClientHello.java index babf2bb452d..e75076b11d6 100644 --- a/src/java.base/share/classes/sun/security/ssl/ClientHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ClientHello.java @@ -213,8 +213,6 @@ byte[] getHelloCookieBytes() { // ignore cookie hos.putBytes16(getEncodedCipherSuites()); hos.putBytes8(compressionMethod); - extensions.send(hos); // In TLS 1.3, use of certain - // extensions is mandatory. } catch (IOException ioe) { // unlikely } @@ -1426,6 +1424,9 @@ public void consume(ConnectionContext context, shc.handshakeProducers.put(SSLHandshake.SERVER_HELLO.id, SSLHandshake.SERVER_HELLO); + // Reset the ClientHello non-zero offset fragment allowance + shc.acceptCliHelloFragments = false; + // // produce // diff --git a/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java b/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java index 337cf76f2c2..e0196f3009c 100644 --- a/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/DTLSInputRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,12 +40,23 @@ final class DTLSInputRecord extends InputRecord implements DTLSRecord { private DTLSReassembler reassembler = null; private int readEpoch; + private SSLContextImpl sslContext; DTLSInputRecord(HandshakeHash handshakeHash) { super(handshakeHash, SSLReadCipher.nullDTlsReadCipher()); this.readEpoch = 0; } + // Method to set TransportContext + public void setTransportContext(TransportContext tc) { + this.tc = tc; + } + + // Method to set SSLContext + public void setSSLContext(SSLContextImpl sslContext) { + this.sslContext = sslContext; + } + @Override void changeReadCiphers(SSLReadCipher readCipher) { this.readCipher = readCipher; @@ -537,6 +548,27 @@ public int compareTo(RecordFragment o) { } } + /** + * Turn a sufficiently-large initial ClientHello fragment into one that + * stops immediately after the compression methods. This is only used + * for the initial CH message fragment at offset 0. + * + * @param srcFrag the fragment actually received by the DTLSReassembler + * @param limit the size of the new, cloned/truncated handshake fragment + * + * @return a truncated handshake fragment that is sized to look like a + * complete message, but actually contains only up to the compression + * methods (no extensions) + */ + private static HandshakeFragment truncateChFragment(HandshakeFragment srcFrag, + int limit) { + return new HandshakeFragment(Arrays.copyOf(srcFrag.fragment, limit), + srcFrag.contentType, srcFrag.majorVersion, + srcFrag.minorVersion, srcFrag.recordEnS, srcFrag.recordEpoch, + srcFrag.recordSeq, srcFrag.handshakeType, limit, + srcFrag.messageSeq, srcFrag.fragmentOffset, limit); + } + private static final class HoleDescriptor { int offset; // fragment_offset int limit; // fragment_offset + fragment_length @@ -640,10 +672,17 @@ void expectingFinishFlight() { // Queue up a handshake message. void queueUpHandshake(HandshakeFragment hsf) throws SSLProtocolException { if (!isDesirable(hsf)) { - // Not a dedired record, discard it. + // Not a desired record, discard it. return; } + if (hsf.handshakeType == SSLHandshake.CLIENT_HELLO.id) { + // validate the first or subsequent ClientHello message + if ((hsf = valHello(hsf, hsf.messageSeq == 0)) == null) { + return; + } + } + // Clean up the retransmission messages if necessary. cleanUpRetransmit(hsf); @@ -769,6 +808,100 @@ void queueUpHandshake(HandshakeFragment hsf) throws SSLProtocolException { } } + private HandshakeFragment valHello(HandshakeFragment hsf, + boolean firstHello) { + ServerHandshakeContext shc = + (ServerHandshakeContext) tc.handshakeContext; + // Drop any fragment that is not a zero offset until we've received + // a second (or possibly later) CH message that passes the cookie + // check. + if (shc == null || !shc.acceptCliHelloFragments) { + if (hsf.fragmentOffset != 0) { + return null; + } + } else { + // Let this fragment through to the DTLSReassembler as-is + return hsf; + } + + try { + ByteBuffer fragmentData = ByteBuffer.wrap(hsf.fragment); + + ProtocolVersion pv = ProtocolVersion.valueOf( + Record.getInt16(fragmentData)); + if (!pv.isDTLS) { + return null; + } + // Read the random (32 bytes) + if (fragmentData.remaining() < 32) { + if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + SSLLogger.fine("Rejected client hello fragment (bad random len) " + + "fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength); + } + return null; + } + fragmentData.position(fragmentData.position() + 32); + + // SessionID + byte[] sessId = Record.getBytes8(fragmentData); + if (sessId.length > 0 && + !SSLConfiguration.enableDtlsResumeCookie) { + // If we are in a resumption it is possible that the cookie + // exchange will be skipped. This is a server-side setting + // and it is NOT the default. If enableDtlsResumeCookie is + // false though, then we will buffer fragments since there + // is no cookie exchange to execute prior to performing + // reassembly. + return hsf; + } + + // Cookie + byte[] cookie = Record.getBytes8(fragmentData); + if (firstHello && cookie.length != 0) { + if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + SSLLogger.fine("Rejected initial client hello fragment (bad cookie len) " + + "fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength); + } + return null; + } + // CipherSuites + Record.getBytes16(fragmentData); + // Compression methods + Record.getBytes8(fragmentData); + + // If it's the first fragment, we'll truncate it and push it + // through the reassembler. + if (firstHello) { + return truncateChFragment(hsf, fragmentData.position()); + } else { + HelloCookieManager hcMgr = sslContext. + getHelloCookieManager(ProtocolVersion.DTLS10); + ByteBuffer msgFragBuf = ByteBuffer.wrap(hsf.fragment, 0, + fragmentData.position()); + ClientHello.ClientHelloMessage chMsg = + new ClientHello.ClientHelloMessage(shc, msgFragBuf, null); + if (!hcMgr.isCookieValid(shc, chMsg, cookie)) { + // Bad cookie check, truncate it and let the ClientHello + // consumer recheck, fail and take the appropriate action. + return truncateChFragment(hsf, fragmentData.position()); + } else { + // It's a good cookie, return the original handshake + // fragment and let it go into the DTLSReassembler like + // any other fragment so we can wait for the rest of + // the CH message. + shc.acceptCliHelloFragments = true; + return hsf; + } + } + } catch (IOException ioe) { + if (SSLLogger.isOn && SSLLogger.isOn("verbose")) { + SSLLogger.fine("Rejected client hello fragment " + + "fo=" + hsf.fragmentOffset + " fl=" + hsf.fragmentLength); + } + return null; + } + } + // Queue up a ChangeCipherSpec message void queueUpChangeCipherSpec(RecordFragment rf) throws SSLProtocolException { diff --git a/src/java.base/share/classes/sun/security/ssl/ServerHandshakeContext.java b/src/java.base/share/classes/sun/security/ssl/ServerHandshakeContext.java index 829fa2af96c..11b625e5791 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHandshakeContext.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHandshakeContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,6 +55,7 @@ class ServerHandshakeContext extends HandshakeContext { CertificateMessage.CertificateEntry currentCertEntry; private static final long DEFAULT_STATUS_RESP_DELAY = 5000L; final long statusRespTimeout; + boolean acceptCliHelloFragments = false; ServerHandshakeContext(SSLContextImpl sslContext, diff --git a/src/java.base/share/classes/sun/security/ssl/TransportContext.java b/src/java.base/share/classes/sun/security/ssl/TransportContext.java index c235da3068c..f65a08dfcfe 100644 --- a/src/java.base/share/classes/sun/security/ssl/TransportContext.java +++ b/src/java.base/share/classes/sun/security/ssl/TransportContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -156,6 +156,11 @@ private TransportContext(SSLContextImpl sslContext, SSLTransport transport, this.acc = AccessController.getContext(); this.consumers = new HashMap<>(); + + if (inputRecord instanceof DTLSInputRecord dtlsInputRecord) { + dtlsInputRecord.setTransportContext(this); + dtlsInputRecord.setSSLContext(this.sslContext); + } } // Dispatch plaintext to a specific consumer. diff --git a/test/jdk/javax/net/ssl/DTLS/InvalidRecords.java b/test/jdk/javax/net/ssl/DTLS/InvalidRecords.java index 304cb0695d6..120e6b258e6 100644 --- a/test/jdk/javax/net/ssl/DTLS/InvalidRecords.java +++ b/test/jdk/javax/net/ssl/DTLS/InvalidRecords.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ /* * @test - * @bug 8043758 + * @bug 8043758 8307383 * @summary Datagram Transport Layer Security (DTLS) * @modules java.base/sun.security.util * @library /test/lib @@ -36,6 +36,7 @@ import java.net.DatagramPacket; import java.net.SocketAddress; +import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -73,11 +74,34 @@ DatagramPacket createHandshakePacket(byte[] ba, SocketAddress socketAddr) { // ClientHello with cookie needInvalidRecords.set(false); System.out.println("invalidate ClientHello message"); - if (ba[ba.length - 1] == (byte)0xFF) { - ba[ba.length - 1] = (byte)0xFE; + // We will alter the compression method field in order to make the cookie + // check fail. + ByteBuffer chRec = ByteBuffer.wrap(ba); + // Skip 59 bytes past the record header (13), the handshake header (12), + // the protocol version (2), and client random (32) + chRec.position(59); + // Jump past the session ID + int len = Byte.toUnsignedInt(chRec.get()); + chRec.position(chRec.position() + len); + // Skip the cookie + len = Byte.toUnsignedInt(chRec.get()); + chRec.position(chRec.position() + len); + // Skip past cipher suites + len = Short.toUnsignedInt(chRec.getShort()); + chRec.position(chRec.position() + len); + // Read the data on the compression methods, should be at least 1 + len = Byte.toUnsignedInt(chRec.get()); + if (len >= 1) { + System.out.println("Detected compression methods (count = " + len + ")"); } else { ba[ba.length - 1] = (byte)0xFF; + throw new RuntimeException("Got zero length comp methods"); } + // alter the first comp method. + int compMethodVal = Byte.toUnsignedInt(chRec.get(chRec.position())); + System.out.println("Changing value at position " + chRec.position() + + " from " + compMethodVal + " to " + ++compMethodVal); + chRec.put(chRec.position(), (byte)compMethodVal); } return super.createHandshakePacket(ba, socketAddr); diff --git a/test/jdk/javax/net/ssl/TLSCommon/MFLNTest.java b/test/jdk/javax/net/ssl/TLSCommon/MFLNTest.java index ee50f21ae27..0867925f135 100644 --- a/test/jdk/javax/net/ssl/TLSCommon/MFLNTest.java +++ b/test/jdk/javax/net/ssl/TLSCommon/MFLNTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,7 +34,15 @@ public class MFLNTest extends SSLEngineTestCase { public static void main(String[] args) { setUpAndStartKDCIfNeeded(); System.setProperty("jsse.enableMFLNExtension", "true"); - for (int mfl = 4096; mfl >= 256; mfl /= 2) { + String testMode = System.getProperty("test.mode", "norm"); + int mflLen; + if (testMode.equals("norm_sni")) { + mflLen = 512; + } else { + mflLen = 256; + } + + for (int mfl = 4096; mfl >= mflLen; mfl /= 2) { System.out.println("==============================================" + "=============="); System.out.printf("Testsing DTLS handshake with MFL = %d%n", mfl); From fbbf2d32e4fbef17e597af3dfa7ca419b067e740 Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Fri, 23 Aug 2024 15:09:22 -0700 Subject: [PATCH 2/7] 8328286: Enhance HTTP client Reviewed-by: mbalao Backport-of: cf8dc79f392c8ec3414d8b36803f026852c4e386 --- .../java/net/doc-files/net-properties.html | 9 + .../classes/sun/net/www/MessageHeader.java | 55 ++++ .../www/protocol/http/HttpURLConnection.java | 23 +- src/java.base/share/conf/net.properties | 17 + .../jdk/internal/net/http/Exchange.java | 38 ++- .../internal/net/http/Http1HeaderParser.java | 49 ++- .../internal/net/http/Http2ClientImpl.java | 78 +++-- .../internal/net/http/Http2Connection.java | 292 ++++++++++++++---- .../jdk/internal/net/http/HttpClientImpl.java | 4 +- .../internal/net/http/HttpRequestImpl.java | 4 +- .../net/http/ResponseBodyHandlers.java | 12 +- .../classes/jdk/internal/net/http/Stream.java | 118 +++++-- .../net/http/common/HeaderDecoder.java | 6 +- .../jdk/internal/net/http/common/Utils.java | 13 + .../jdk/internal/net/http/hpack/Decoder.java | 80 ++++- .../net/http/hpack/DecodingCallback.java | 14 + .../jdk/internal/net/http/hpack/Encoder.java | 14 +- .../share/classes/module-info.java | 30 +- .../share/classes/module-info.java | 12 +- .../classes/sun/net/httpserver/Request.java | 35 ++- .../sun/net/httpserver/ServerConfig.java | 18 +- test/jdk/java/net/httpclient/ShutdownNow.java | 1 + .../http2/PushPromiseContinuation.java | 9 +- .../test/lib/common/HttpServerAdapters.java | 12 +- .../test/lib/http2/HpackTestEncoder.java | 174 +++++++++++ .../test/lib/http2/Http2TestExchange.java | 13 + .../test/lib/http2/Http2TestExchangeImpl.java | 16 +- .../lib/http2/Http2TestServerConnection.java | 158 ++++++++-- 28 files changed, 1122 insertions(+), 182 deletions(-) create mode 100644 test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/HpackTestEncoder.java diff --git a/src/java.base/share/classes/java/net/doc-files/net-properties.html b/src/java.base/share/classes/java/net/doc-files/net-properties.html index 85af2487bf1..fc401e90b6b 100644 --- a/src/java.base/share/classes/java/net/doc-files/net-properties.html +++ b/src/java.base/share/classes/java/net/doc-files/net-properties.html @@ -248,6 +248,15 @@

Misc HTTP URL stream protocol handler properties

The channel binding tokens generated are of the type "tls-server-end-point" as defined in RFC 5929.

+ +
  • {@systemProperty jdk.http.maxHeaderSize} (default: 393216 or 384kB)
    + This is the maximum header field section size that a client is prepared to accept. + This is computed as the sum of the size of the uncompressed header name, plus + the size of the uncompressed header value, plus an overhead of 32 bytes for + each field section line. If a peer sends a field section that exceeds this + size a {@link java.net.ProtocolException ProtocolException} will be raised. + This applies to all versions of the HTTP protocol. A value of zero or a negative + value means no limit. If left unspecified, the default value is 393216 bytes.

    All these properties are checked only once at startup.

    diff --git a/src/java.base/share/classes/sun/net/www/MessageHeader.java b/src/java.base/share/classes/sun/net/www/MessageHeader.java index 5542193b9a9..9cad6ee3c2f 100644 --- a/src/java.base/share/classes/sun/net/www/MessageHeader.java +++ b/src/java.base/share/classes/sun/net/www/MessageHeader.java @@ -30,6 +30,8 @@ package sun.net.www; import java.io.*; +import java.lang.reflect.Array; +import java.net.ProtocolException; import java.util.Collections; import java.util.*; @@ -46,11 +48,32 @@ class MessageHeader { private String values[]; private int nkeys; + // max number of bytes for headers, <=0 means unlimited; + // this corresponds to the length of the names, plus the length + // of the values, plus an overhead of 32 bytes per name: value + // pair. + // Note: we use the same definition as HTTP/2 SETTINGS_MAX_HEADER_LIST_SIZE + // see RFC 9113, section 6.5.2. + // https://www.rfc-editor.org/rfc/rfc9113.html#SETTINGS_MAX_HEADER_LIST_SIZE + private final int maxHeaderSize; + + // Aggregate size of the field lines (name + value + 32) x N + // that have been parsed and accepted so far. + // This is defined as a long to force promotion to long + // and avoid overflows; see checkNewSize; + private long size; + public MessageHeader () { + this(0); + } + + public MessageHeader (int maxHeaderSize) { + this.maxHeaderSize = maxHeaderSize; grow(); } public MessageHeader (InputStream is) throws java.io.IOException { + maxHeaderSize = 0; parseHeader(is); } @@ -477,10 +500,28 @@ public static String canonicalID(String id) { public void parseHeader(InputStream is) throws java.io.IOException { synchronized (this) { nkeys = 0; + size = 0; } mergeHeader(is); } + private void checkMaxHeaderSize(int sz) throws ProtocolException { + if (maxHeaderSize > 0) checkNewSize(size, sz, 0); + } + + private long checkNewSize(long size, int name, int value) throws ProtocolException { + // See SETTINGS_MAX_HEADER_LIST_SIZE, RFC 9113, section 6.5.2. + long newSize = size + name + value + 32; + if (maxHeaderSize > 0 && newSize > maxHeaderSize) { + Arrays.fill(keys, 0, nkeys, null); + Arrays.fill(values,0, nkeys, null); + nkeys = 0; + throw new ProtocolException(String.format("Header size too big: %s > %s", + newSize, maxHeaderSize)); + } + return newSize; + } + /** Parse and merge a MIME header from an input stream. */ @SuppressWarnings("fallthrough") public void mergeHeader(InputStream is) throws java.io.IOException { @@ -494,7 +535,15 @@ public void mergeHeader(InputStream is) throws java.io.IOException { int c; boolean inKey = firstc > ' '; s[len++] = (char) firstc; + checkMaxHeaderSize(len); parseloop:{ + // We start parsing for a new name value pair here. + // The max header size includes an overhead of 32 bytes per + // name value pair. + // See SETTINGS_MAX_HEADER_LIST_SIZE, RFC 9113, section 6.5.2. + long maxRemaining = maxHeaderSize > 0 + ? maxHeaderSize - size - 32 + : Long.MAX_VALUE; while ((c = is.read()) >= 0) { switch (c) { case ':': @@ -528,6 +577,9 @@ public void mergeHeader(InputStream is) throws java.io.IOException { s = ns; } s[len++] = (char) c; + if (maxHeaderSize > 0 && len > maxRemaining) { + checkMaxHeaderSize(len); + } } firstc = -1; } @@ -549,6 +601,9 @@ public void mergeHeader(InputStream is) throws java.io.IOException { v = new String(); else v = String.copyValueOf(s, keyend, len - keyend); + int klen = k == null ? 0 : k.length(); + + size = checkNewSize(size, klen, v.length()); add(k, v); } } diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index cbd24ee2f0b..2f72334b66d 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -172,6 +172,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection { */ private static int bufSize4ES = 0; + private static final int maxHeaderSize; + /* * Restrict setting of request headers through the public api * consistent with JavaScript XMLHttpRequest2 with a few @@ -286,6 +288,19 @@ private static Set schemesListToSet(String list) { } else { restrictedHeaderSet = null; } + + int defMaxHeaderSize = 384 * 1024; + String maxHeaderSizeStr = getNetProperty("jdk.http.maxHeaderSize"); + int maxHeaderSizeVal = defMaxHeaderSize; + if (maxHeaderSizeStr != null) { + try { + maxHeaderSizeVal = Integer.parseInt(maxHeaderSizeStr); + } catch (NumberFormatException n) { + maxHeaderSizeVal = defMaxHeaderSize; + } + } + if (maxHeaderSizeVal < 0) maxHeaderSizeVal = 0; + maxHeaderSize = maxHeaderSizeVal; } static final String httpVersion = "HTTP/1.1"; @@ -755,7 +770,7 @@ private void writeRequests() throws IOException { } ps = (PrintStream) http.getOutputStream(); connected=true; - responses = new MessageHeader(); + responses = new MessageHeader(maxHeaderSize); setRequests=false; writeRequests(); } @@ -913,7 +928,7 @@ protected HttpURLConnection(URL u, Proxy p, Handler handler) throws IOException { super(checkURL(u)); requests = new MessageHeader(); - responses = new MessageHeader(); + responses = new MessageHeader(maxHeaderSize); userHeaders = new MessageHeader(); this.handler = handler; instProxy = p; @@ -2838,7 +2853,7 @@ private boolean followRedirect0(String loc, int stat, URL locUrl) } // clear out old response headers!!!! - responses = new MessageHeader(); + responses = new MessageHeader(maxHeaderSize); if (stat == HTTP_USE_PROXY) { /* This means we must re-request the resource through the * proxy denoted in the "Location:" field of the response. @@ -3028,7 +3043,7 @@ private void reset() throws IOException { } catch (IOException e) { } } responseCode = -1; - responses = new MessageHeader(); + responses = new MessageHeader(maxHeaderSize); connected = false; } diff --git a/src/java.base/share/conf/net.properties b/src/java.base/share/conf/net.properties index 67f294355a1..2aa9a9630be 100644 --- a/src/java.base/share/conf/net.properties +++ b/src/java.base/share/conf/net.properties @@ -130,3 +130,20 @@ jdk.http.auth.tunneling.disabledSchemes=Basic #jdk.http.ntlm.transparentAuth=trustedHosts # jdk.http.ntlm.transparentAuth=disabled + +# +# Maximum HTTP field section size that a client is prepared to accept +# +# jdk.http.maxHeaderSize=393216 +# +# This is the maximum header field section size that a client is prepared to accept. +# This is computed as the sum of the size of the uncompressed header name, plus +# the size of the uncompressed header value, plus an overhead of 32 bytes for +# each field section line. If a peer sends a field section that exceeds this +# size a {@link java.net.ProtocolException ProtocolException} will be raised. +# This applies to all versions of the HTTP protocol. A value of zero or a negative +# value means no limit. If left unspecified, the default value is 393216 bytes +# or 384kB. +# +# Note: This property is currently used by the JDK Reference implementation. It +# is not guaranteed to be examined and used by other implementations. diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java b/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java index e643b05422a..daed0678749 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Exchange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,7 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.net.http.HttpClient; import java.net.http.HttpHeaders; @@ -67,6 +68,8 @@ */ final class Exchange { + static final int MAX_NON_FINAL_RESPONSES = + Utils.getIntegerNetProperty("jdk.httpclient.maxNonFinalResponses", 8); final Logger debug = Utils.getDebugLogger(this::dbgString, Utils.DEBUG); final HttpRequestImpl request; @@ -91,6 +94,8 @@ final class Exchange { // exchange so that it can be aborted/timed out mid setup. final ConnectionAborter connectionAborter = new ConnectionAborter(); + final AtomicInteger nonFinalResponses = new AtomicInteger(); + Exchange(HttpRequestImpl request, MultiExchange multi) { this.request = request; this.upgrading = false; @@ -357,7 +362,7 @@ CompletableFuture checkCancelled(CompletableFuture cf, HttpConnection public void h2Upgrade() { upgrading = true; - request.setH2Upgrade(client.client2()); + request.setH2Upgrade(this); } synchronized IOException getCancelCause() { @@ -458,6 +463,7 @@ private CompletableFuture expectContinue(ExchangeImpl ex) { Log.logResponse(r1::toString); int rcode = r1.statusCode(); if (rcode == 100) { + nonFinalResponses.incrementAndGet(); Log.logTrace("Received 100-Continue: sending body"); if (debug.on()) debug.log("Received 100-Continue for %s", r1); CompletableFuture cf = @@ -534,12 +540,20 @@ private CompletableFuture ignore1xxResponse(final Response rsp) { + rsp.statusCode()); } assert exchImpl != null : "Illegal state - current exchange isn't set"; - // ignore this Response and wait again for the subsequent response headers - final CompletableFuture cf = exchImpl.getResponseAsync(parentExecutor); - // we recompose the CF again into the ignore1xxResponse check/function because - // the 1xx response is allowed to be sent multiple times for a request, before - // a final response arrives - return cf.thenCompose(this::ignore1xxResponse); + int count = nonFinalResponses.incrementAndGet(); + if (MAX_NON_FINAL_RESPONSES > 0 && (count < 0 || count > MAX_NON_FINAL_RESPONSES)) { + return MinimalFuture.failedFuture( + new ProtocolException(String.format( + "Too many interim responses received: %s > %s", + count, MAX_NON_FINAL_RESPONSES))); + } else { + // ignore this Response and wait again for the subsequent response headers + final CompletableFuture cf = exchImpl.getResponseAsync(parentExecutor); + // we recompose the CF again into the ignore1xxResponse check/function because + // the 1xx response is allowed to be sent multiple times for a request, before + // a final response arrives + return cf.thenCompose(this::ignore1xxResponse); + } } else { // return the already completed future return MinimalFuture.completedFuture(rsp); @@ -804,6 +818,14 @@ HttpClient.Version version() { return multi.version(); } + boolean pushEnabled() { + return pushGroup != null; + } + + String h2cSettingsStrings() { + return client.client2().getSettingsString(pushEnabled()); + } + String dbgString() { return dbgTag; } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java index 669c173e3f8..8c796193015 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http1HeaderParser.java @@ -25,6 +25,7 @@ package jdk.internal.net.http; +import java.io.IOException; import java.net.ProtocolException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; @@ -53,6 +54,12 @@ class Http1HeaderParser { private int responseCode; private HttpHeaders headers; private Map> privateMap = new HashMap<>(); + private long size; + + private static final int K = 1024; + private static final int MAX_HTTP_HEADER_SIZE = Utils.getIntegerNetProperty( + "jdk.http.maxHeaderSize", + Integer.MIN_VALUE, Integer.MAX_VALUE, 384 * K, true); enum State { INITIAL, STATUS_LINE, @@ -164,11 +171,16 @@ private char get(ByteBuffer input) { return (char)(input.get() & 0xFF); } - private void readResumeStatusLine(ByteBuffer input) { + private void readResumeStatusLine(ByteBuffer input) throws ProtocolException { + final long max = MAX_HTTP_HEADER_SIZE - size - 32 - sb.length(); + int count = 0; char c = 0; while (input.hasRemaining() && (c = get(input)) != CR) { if (c == LF) break; sb.append(c); + if (++count > max) { + checkMaxHeaderSize(sb.length()); + } } if (c == CR) { state = State.STATUS_LINE_FOUND_CR; @@ -185,6 +197,7 @@ private void readStatusLineFeed(ByteBuffer input) throws ProtocolException { } statusLine = sb.toString(); + size = size + 32 + statusLine.length(); sb = new StringBuilder(); if (!statusLine.startsWith("HTTP/1.")) { throw protocolException("Invalid status line: \"%s\"", statusLine); @@ -205,7 +218,23 @@ private void readStatusLineFeed(ByteBuffer input) throws ProtocolException { state = State.STATUS_LINE_END; } - private void maybeStartHeaders(ByteBuffer input) { + private void checkMaxHeaderSize(int sz) throws ProtocolException { + long s = size + sz + 32; + if (MAX_HTTP_HEADER_SIZE > 0 && s > MAX_HTTP_HEADER_SIZE) { + throw new ProtocolException(String.format("Header size too big: %s > %s", + s, MAX_HTTP_HEADER_SIZE)); + } + } + static private long newSize(long size, int name, int value) throws ProtocolException { + long newSize = size + name + value + 32; + if (MAX_HTTP_HEADER_SIZE > 0 && newSize > MAX_HTTP_HEADER_SIZE) { + throw new ProtocolException(String.format("Header size too big: %s > %s", + newSize, MAX_HTTP_HEADER_SIZE)); + } + return newSize; + } + + private void maybeStartHeaders(ByteBuffer input) throws ProtocolException { assert state == State.STATUS_LINE_END; assert sb.length() == 0; char c = get(input); @@ -215,6 +244,7 @@ private void maybeStartHeaders(ByteBuffer input) { state = State.STATUS_LINE_END_LF; } else { sb.append(c); + checkMaxHeaderSize(sb.length()); state = State.HEADER; } } @@ -232,9 +262,11 @@ private void maybeEndHeaders(ByteBuffer input) throws ProtocolException { } } - private void readResumeHeader(ByteBuffer input) { + private void readResumeHeader(ByteBuffer input) throws ProtocolException { assert state == State.HEADER; assert input.hasRemaining(); + final long max = MAX_HTTP_HEADER_SIZE - size - 32 - sb.length(); + int count = 0; while (input.hasRemaining()) { char c = get(input); if (c == CR) { @@ -248,6 +280,9 @@ private void readResumeHeader(ByteBuffer input) { if (c == HT) c = SP; sb.append(c); + if (++count > max) { + checkMaxHeaderSize(sb.length()); + } } } @@ -268,12 +303,12 @@ private void addHeaderFromString(String headerString) throws ProtocolException { if (!Utils.isValidValue(value)) { throw protocolException("Invalid header value \"%s: %s\"", name, value); } - + size = newSize(size, name.length(), value.length()); privateMap.computeIfAbsent(name.toLowerCase(Locale.US), k -> new ArrayList<>()).add(value); } - private void resumeOrLF(ByteBuffer input) { + private void resumeOrLF(ByteBuffer input) throws ProtocolException { assert state == State.HEADER_FOUND_CR || state == State.HEADER_FOUND_LF; char c = state == State.HEADER_FOUND_LF ? LF : get(input); if (c == LF) { @@ -283,10 +318,12 @@ private void resumeOrLF(ByteBuffer input) { state = State.HEADER_FOUND_CR_LF; } else if (c == SP || c == HT) { sb.append(SP); // parity with MessageHeaders + checkMaxHeaderSize(sb.length()); state = State.HEADER; } else { sb = new StringBuilder(); sb.append(c); + checkMaxHeaderSize(1); state = State.HEADER; } } @@ -312,6 +349,7 @@ private void resumeOrSecondCR(ByteBuffer input) throws ProtocolException { } else if (c == SP || c == HT) { assert sb.length() != 0; sb.append(SP); // continuation line + checkMaxHeaderSize(sb.length()); state = State.HEADER; } else { if (sb.length() > 0) { @@ -322,6 +360,7 @@ private void resumeOrSecondCR(ByteBuffer input) throws ProtocolException { addHeaderFromString(headerString); } sb.append(c); + checkMaxHeaderSize(sb.length()); state = State.HEADER; } } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java index f77e1428f2c..a302006cae3 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2ClientImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.locks.ReentrantLock; -import jdk.internal.net.http.common.Log; import jdk.internal.net.http.common.Logger; import jdk.internal.net.http.common.MinimalFuture; import jdk.internal.net.http.common.Utils; @@ -46,6 +45,7 @@ import static jdk.internal.net.http.frame.SettingsFrame.HEADER_TABLE_SIZE; import static jdk.internal.net.http.frame.SettingsFrame.MAX_CONCURRENT_STREAMS; import static jdk.internal.net.http.frame.SettingsFrame.MAX_FRAME_SIZE; +import static jdk.internal.net.http.frame.SettingsFrame.MAX_HEADER_LIST_SIZE; /** * Http2 specific aspects of HttpClientImpl @@ -98,16 +98,20 @@ class Http2ClientImpl { CompletableFuture getConnectionFor(HttpRequestImpl req, Exchange exchange) { String key = Http2Connection.keyFor(req); + boolean pushEnabled = exchange.pushEnabled(); connectionPoolLock.lock(); try { Http2Connection connection = connections.get(key); if (connection != null) { try { - if (!connection.tryReserveForPoolCheckout() || !connection.reserveStream(true)) { + if (!connection.tryReserveForPoolCheckout() + || !connection.reserveStream(true, pushEnabled)) { if (debug.on()) debug.log("removing connection from pool since it couldn't be" + - " reserved for use: %s", connection); + " reserved for use%s: %s", + pushEnabled ? " with server push enabled" : "", + connection); removeFromPool(connection); } else { // fast path if connection already exists @@ -137,7 +141,7 @@ CompletableFuture getConnectionFor(HttpRequestImpl req, try { if (conn != null) { try { - conn.reserveStream(true); + conn.reserveStream(true, exchange.pushEnabled()); } catch (IOException e) { throw new UncheckedIOException(e); // shouldn't happen } @@ -183,10 +187,21 @@ boolean offerConnection(Http2Connection c) { } Http2Connection c1 = connections.putIfAbsent(key, c); if (c1 != null) { - c.setFinalStream(); - if (debug.on()) - debug.log("existing entry in connection pool for %s", key); - return false; + if (c.serverPushEnabled() && !c1.serverPushEnabled()) { + c1.setFinalStream(); + connections.remove(key, c1); + connections.put(key, c); + if (debug.on()) { + debug.log("Replacing %s with %s in connection pool", c1, c); + } + if (c1.shouldClose()) c1.close(); + return true; + } else { + c.setFinalStream(); + if (debug.on()) + debug.log("existing entry in connection pool for %s", key); + return false; + } } if (debug.on()) debug.log("put in the connection pool: %s", c); @@ -248,8 +263,8 @@ HttpClientImpl client() { } /** Returns the client settings as a base64 (url) encoded string */ - String getSettingsString() { - SettingsFrame sf = getClientSettings(); + String getSettingsString(boolean defaultServerPush) { + SettingsFrame sf = getClientSettings(defaultServerPush); byte[] settings = sf.toByteArray(); // without the header Base64.Encoder encoder = Base64.getUrlEncoder() .withoutPadding(); @@ -259,14 +274,7 @@ String getSettingsString() { private static final int K = 1024; private static int getParameter(String property, int min, int max, int defaultValue) { - int value = Utils.getIntegerNetProperty(property, defaultValue); - // use default value if misconfigured - if (value < min || value > max) { - Log.logError("Property value for {0}={1} not in [{2}..{3}]: " + - "using default={4}", property, value, min, max, defaultValue); - value = defaultValue; - } - return value; + return Utils.getIntegerNetProperty(property, min, max, defaultValue, true); } // used for the connection window, to have a connection window size @@ -286,7 +294,18 @@ int getConnectionWindowSize(SettingsFrame clientSettings) { streamWindow, Integer.MAX_VALUE, defaultValue); } - SettingsFrame getClientSettings() { + /** + * This method is used to test whether pushes are globally + * disabled on all connections. + * @return true if pushes are globally disabled on all connections + */ + boolean serverPushDisabled() { + return getParameter( + "jdk.httpclient.enablepush", + 0, 1, 1) == 0; + } + + SettingsFrame getClientSettings(boolean defaultServerPush) { SettingsFrame frame = new SettingsFrame(); // default defined for HTTP/2 is 4 K, we use 16 K. frame.setParameter(HEADER_TABLE_SIZE, getParameter( @@ -295,14 +314,15 @@ SettingsFrame getClientSettings() { // O: does not accept push streams. 1: accepts push streams. frame.setParameter(ENABLE_PUSH, getParameter( "jdk.httpclient.enablepush", - 0, 1, 1)); + 0, 1, defaultServerPush ? 1 : 0)); // HTTP/2 recommends to set the number of concurrent streams - // no lower than 100. We use 100. 0 means no stream would be - // accepted. That would render the client to be non functional, - // so we won't let 0 be configured for our Http2ClientImpl. + // no lower than 100. We use 100, unless push promises are + // disabled. + int initialServerStreams = frame.getParameter(ENABLE_PUSH) == 0 + ? 0 : 100; frame.setParameter(MAX_CONCURRENT_STREAMS, getParameter( "jdk.httpclient.maxstreams", - 1, Integer.MAX_VALUE, 100)); + 0, Integer.MAX_VALUE, initialServerStreams)); // Maximum size is 2^31-1. Don't allow window size to be less // than the minimum frame size as this is likely to be a // configuration error. HTTP/2 specify a default of 64 * K -1, @@ -315,6 +335,14 @@ SettingsFrame getClientSettings() { frame.setParameter(MAX_FRAME_SIZE, getParameter( "jdk.httpclient.maxframesize", 16 * K, 16 * K * K -1, 16 * K)); + // Maximum field section size we're prepared to accept + // This is the uncompressed name + value size + 32 per field line + int maxHeaderSize = getParameter( + "jdk.http.maxHeaderSize", + Integer.MIN_VALUE, Integer.MAX_VALUE, 384 * K); + // If the property is <= 0 the value is unlimited + if (maxHeaderSize <= 0) maxHeaderSize = -1; + frame.setParameter(MAX_HEADER_LIST_SIZE, maxHeaderSize); return frame; } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java b/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java index cffa5767512..84c09d6bca6 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Http2Connection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.net.InetSocketAddress; +import java.net.ProtocolException; import java.net.URI; import java.net.http.HttpConnectTimeoutException; import java.nio.ByteBuffer; @@ -47,6 +48,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Flow; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Function; @@ -321,6 +324,45 @@ void markPrefaceSent() { } } + private final class PushPromiseDecoder extends HeaderDecoder implements DecodingCallback { + + final int parentStreamId; + final int pushPromiseStreamId; + final Stream parent; + final AtomicReference errorRef = new AtomicReference<>(); + + PushPromiseDecoder(int parentStreamId, int pushPromiseStreamId, Stream parent) { + this.parentStreamId = parentStreamId; + this.pushPromiseStreamId = pushPromiseStreamId; + this.parent = parent; + } + + @Override + protected void addHeader(String name, String value) { + if (errorRef.get() == null) { + super.addHeader(name, value); + } + } + + @Override + public void onMaxHeaderListSizeReached(long size, int maxHeaderListSize) throws ProtocolException { + try { + DecodingCallback.super.onMaxHeaderListSizeReached(size, maxHeaderListSize); + } catch (ProtocolException pe) { + if (parent != null) { + if (errorRef.compareAndSet(null, pe)) { + // cancel the parent stream + resetStream(pushPromiseStreamId, ResetFrame.REFUSED_STREAM); + parent.onProtocolError(pe); + } + } else { + // interrupt decoding and closes the connection + throw pe; + } + } + } + } + private static final int HALF_CLOSED_LOCAL = 1; private static final int HALF_CLOSED_REMOTE = 2; @@ -349,7 +391,7 @@ void markPrefaceSent() { private final Decoder hpackIn; final SettingsFrame clientSettings; private volatile SettingsFrame serverSettings; - private record PushContinuationState(HeaderDecoder pushContDecoder, PushPromiseFrame pushContFrame) {} + private record PushContinuationState(PushPromiseDecoder pushContDecoder, PushPromiseFrame pushContFrame) {} private volatile PushContinuationState pushContinuationState; private final String key; // for HttpClientImpl.connections map private final FramesDecoder framesDecoder; @@ -363,12 +405,24 @@ private record PushContinuationState(HeaderDecoder pushContDecoder, PushPromiseF private final FramesController framesController = new FramesController(); private final Http2TubeSubscriber subscriber; final ConnectionWindowUpdateSender windowUpdater; - private volatile Throwable cause; + private final AtomicReference cause = new AtomicReference<>(); private volatile Supplier initial; private volatile Stream initialStream; + private ValidatingHeadersConsumer orphanedConsumer; + private final AtomicInteger orphanedHeaders = new AtomicInteger(); + static final int DEFAULT_FRAME_SIZE = 16 * 1024; + static final int MAX_LITERAL_WITH_INDEXING = + Utils.getIntegerNetProperty("jdk.httpclient.maxLiteralWithIndexing",512); + // The maximum number of HEADER frames, CONTINUATION frames, or PUSH_PROMISE frames + // referring to an already closed or non-existent stream that a client will accept to + // process. Receiving frames referring to non-existent or closed streams doesn't necessarily + // constitute an HTTP/2 protocol error, but receiving too many may indicate a problem + // with the connection. If this limit is reached, a {@link java.net.ProtocolException + // ProtocolException} will be raised and the connection will be closed. + static final int MAX_ORPHANED_HEADERS = 1024; // TODO: need list of control frames from other threads // that need to be sent @@ -376,19 +430,21 @@ private record PushContinuationState(HeaderDecoder pushContDecoder, PushPromiseF private Http2Connection(HttpConnection connection, Http2ClientImpl client2, int nextstreamid, - String key) { + String key, + boolean defaultServerPush) { this.connection = connection; this.client2 = client2; this.subscriber = new Http2TubeSubscriber(client2.client()); this.nextstreamid = nextstreamid; this.key = key; - this.clientSettings = this.client2.getClientSettings(); + this.clientSettings = this.client2.getClientSettings(defaultServerPush); this.framesDecoder = new FramesDecoder(this::processFrame, clientSettings.getParameter(SettingsFrame.MAX_FRAME_SIZE)); // serverSettings will be updated by server this.serverSettings = SettingsFrame.defaultRFCSettings(); this.hpackOut = new Encoder(serverSettings.getParameter(HEADER_TABLE_SIZE)); - this.hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE)); + this.hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE), + clientSettings.getParameter(MAX_HEADER_LIST_SIZE), MAX_LITERAL_WITH_INDEXING); if (debugHpack.on()) { debugHpack.log("For the record:" + super.toString()); debugHpack.log("Decoder created: %s", hpackIn); @@ -407,14 +463,16 @@ private Http2Connection(HttpConnection connection, private Http2Connection(HttpConnection connection, Http2ClientImpl client2, Exchange exchange, - Supplier initial) + Supplier initial, + boolean defaultServerPush) throws IOException, InterruptedException { this(connection, client2, 3, // stream 1 is registered during the upgrade - keyFor(connection)); - reserveStream(true); + keyFor(connection), + defaultServerPush); + reserveStream(true, clientSettings.getFlag(ENABLE_PUSH)); Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize()); Stream initialStream = createStream(exchange); @@ -447,7 +505,8 @@ static CompletableFuture createAsync(HttpConnection connection, Exchange exchange, Supplier initial) { - return MinimalFuture.supply(() -> new Http2Connection(connection, client2, exchange, initial)); + return MinimalFuture.supply(() -> new Http2Connection(connection, client2, exchange, initial, + exchange.pushEnabled())); } // Requires TLS handshake. So, is really async @@ -471,7 +530,8 @@ static CompletableFuture createAsync(HttpRequestImpl request, .thenCompose(notused-> { CompletableFuture cf = new MinimalFuture<>(); try { - Http2Connection hc = new Http2Connection(request, h2client, connection); + Http2Connection hc = new Http2Connection(request, h2client, + connection, exchange.pushEnabled()); cf.complete(hc); } catch (IOException e) { cf.completeExceptionally(e); @@ -486,13 +546,15 @@ static CompletableFuture createAsync(HttpRequestImpl request, */ private Http2Connection(HttpRequestImpl request, Http2ClientImpl h2client, - HttpConnection connection) + HttpConnection connection, + boolean defaultServerPush) throws IOException { this(connection, h2client, 1, - keyFor(request)); + keyFor(request), + defaultServerPush); Log.logTrace("Connection send window size {0} ", windowController.connectionWindowSize()); @@ -515,24 +577,30 @@ final HttpClientImpl client() { // if false returned then a new Http2Connection is required // if true, the stream may be assigned to this connection // for server push, if false returned, then the stream should be cancelled - boolean reserveStream(boolean clientInitiated) throws IOException { + boolean reserveStream(boolean clientInitiated, boolean pushEnabled) throws IOException { stateLock.lock(); try { - return reserveStream0(clientInitiated); + return reserveStream0(clientInitiated, pushEnabled); } finally { stateLock.unlock(); } } - private boolean reserveStream0(boolean clientInitiated) throws IOException { + private boolean reserveStream0(boolean clientInitiated, boolean pushEnabled) throws IOException { if (finalStream()) { return false; } - if (clientInitiated && (lastReservedClientStreamid + 2) >= MAX_CLIENT_STREAM_ID) { + // If requesting to reserve a stream for an exchange for which push is enabled, + // we will reserve the stream in this connection only if this connection is also + // push enabled, unless pushes are globally disabled. + boolean pushCompatible = !clientInitiated || !pushEnabled + || this.serverPushEnabled() + || client2.serverPushDisabled(); + if (clientInitiated && (lastReservedClientStreamid >= MAX_CLIENT_STREAM_ID -2 || !pushCompatible)) { setFinalStream(); client2.removeFromPool(this); return false; - } else if (!clientInitiated && (lastReservedServerStreamid + 2) >= MAX_SERVER_STREAM_ID) { + } else if (!clientInitiated && (lastReservedServerStreamid >= MAX_SERVER_STREAM_ID - 2)) { setFinalStream(); client2.removeFromPool(this); return false; @@ -557,6 +625,15 @@ private boolean reserveStream0(boolean clientInitiated) throws IOException { return true; } + boolean shouldClose() { + stateLock.lock(); + try { + return finalStream() && streams.isEmpty(); + } finally { + stateLock.unlock(); + } + } + /** * Throws an IOException if h2 was not negotiated */ @@ -684,6 +761,10 @@ String key() { return this.key; } + public boolean serverPushEnabled() { + return clientSettings.getParameter(SettingsFrame.ENABLE_PUSH) == 1; + } + boolean offerConnection() { return client2.offerConnection(this); } @@ -786,13 +867,14 @@ final void asyncReceive(ByteBuffer buffer) { } Throwable getRecordedCause() { - return cause; + return cause.get(); } void shutdown(Throwable t) { int state = closedState; if (debug.on()) debug.log(() -> "Shutting down h2c (state="+describeClosedState(state)+"): " + t); if (!markShutdownRequested()) return; + cause.compareAndSet(null, t); if (Log.errors()) { if (t!= null && (!(t instanceof EOFException) || isActive())) { Log.logError(t); @@ -802,9 +884,8 @@ void shutdown(Throwable t) { Log.logError("Shutting down connection"); } } - Throwable initialCause = this.cause; - if (initialCause == null && t != null) this.cause = t; client2.removeFromPool(this); + subscriber.stop(cause.get()); for (Stream s : streams.values()) { try { s.connectionClosing(t); @@ -858,17 +939,39 @@ void processFrame(Http2Frame frame) throws IOException { return; } + if (frame instanceof PushPromiseFrame && !serverPushEnabled()) { + String protocolError = "received a PUSH_PROMISE when SETTINGS_ENABLE_PUSH is 0"; + protocolError(ResetFrame.PROTOCOL_ERROR, protocolError); + return; + } + Stream stream = getStream(streamid); + var nextstreamid = this.nextstreamid; + if (stream == null && (streamid & 0x01) == 0x01 && streamid >= nextstreamid) { + String protocolError = String.format( + "received a frame for a non existing streamid(%s) >= nextstreamid(%s)", + streamid, nextstreamid); + protocolError(ResetFrame.PROTOCOL_ERROR, protocolError); + return; + } if (stream == null && pushContinuationState == null) { // Should never receive a frame with unknown stream id - if (frame instanceof HeaderFrame) { + if (frame instanceof HeaderFrame hf) { + String protocolError = checkMaxOrphanedHeadersExceeded(hf); + if (protocolError != null) { + protocolError(ResetFrame.PROTOCOL_ERROR, protocolError); + return; + } // always decode the headers as they may affect // connection-level HPACK decoding state - DecodingCallback decoder = new ValidatingHeadersConsumer()::onDecoded; + if (orphanedConsumer == null || frame.getClass() != ContinuationFrame.class) { + orphanedConsumer = new ValidatingHeadersConsumer(); + } + DecodingCallback decoder = orphanedConsumer::onDecoded; try { - decodeHeaders((HeaderFrame) frame, decoder); - } catch (UncheckedIOException e) { + decodeHeaders(hf, decoder); + } catch (IOException | UncheckedIOException e) { protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage()); return; } @@ -896,29 +999,41 @@ void processFrame(Http2Frame frame) throws IOException { // While push frame is not null, the only acceptable frame on this // stream is a Continuation frame - if (pushContinuationState != null) { + PushContinuationState pcs = pushContinuationState; + if (pcs != null) { if (frame instanceof ContinuationFrame cf) { + if (stream == null) { + String protocolError = checkMaxOrphanedHeadersExceeded(cf); + if (protocolError != null) { + protocolError(ResetFrame.PROTOCOL_ERROR, protocolError); + return; + } + } try { - if (streamid == pushContinuationState.pushContFrame.streamid()) - handlePushContinuation(stream, cf); - else - protocolError(ErrorFrame.PROTOCOL_ERROR, "Received a Continuation Frame with an " + - "unexpected stream id"); - } catch (UncheckedIOException e) { + if (streamid == pcs.pushContFrame.streamid()) + handlePushContinuation(pcs, stream, cf); + else { + String protocolError = "Received a CONTINUATION with " + + "unexpected stream id: " + streamid + " != " + + pcs.pushContFrame.streamid(); + protocolError(ErrorFrame.PROTOCOL_ERROR, protocolError); + } + } catch (IOException | UncheckedIOException e) { debug.log("Error handling Push Promise with Continuation: " + e.getMessage(), e); protocolError(ErrorFrame.PROTOCOL_ERROR, e.getMessage()); return; } } else { pushContinuationState = null; - protocolError(ErrorFrame.PROTOCOL_ERROR, "Expected a Continuation frame but received " + frame); + String protocolError = "Expected a CONTINUATION frame but received " + frame; + protocolError(ErrorFrame.PROTOCOL_ERROR, protocolError); return; } } else { if (frame instanceof PushPromiseFrame pp) { try { handlePushPromise(stream, pp); - } catch (UncheckedIOException e) { + } catch (IOException | UncheckedIOException e) { protocolError(ErrorFrame.PROTOCOL_ERROR, e.getMessage()); return; } @@ -926,7 +1041,7 @@ void processFrame(Http2Frame frame) throws IOException { // decode headers try { decodeHeaders(hf, stream.rspHeadersConsumer()); - } catch (UncheckedIOException e) { + } catch (IOException | UncheckedIOException e) { debug.log("Error decoding headers: " + e.getMessage(), e); protocolError(ErrorFrame.PROTOCOL_ERROR, e.getMessage()); return; @@ -939,6 +1054,16 @@ void processFrame(Http2Frame frame) throws IOException { } } + private String checkMaxOrphanedHeadersExceeded(HeaderFrame hf) { + if (MAX_ORPHANED_HEADERS > 0 ) { + int orphaned = orphanedHeaders.incrementAndGet(); + if (orphaned < 0 || orphaned > MAX_ORPHANED_HEADERS) { + return "Too many orphaned header frames received on connection"; + } + } + return null; + } + final void dropDataFrame(DataFrame df) { if (isMarked(closedState, SHUTDOWN_REQUESTED)) return; if (debug.on()) { @@ -963,38 +1088,65 @@ final void ensureWindowUpdated(DataFrame df) { private void handlePushPromise(Stream parent, PushPromiseFrame pp) throws IOException { + int promisedStreamid = pp.getPromisedStream(); + if ((promisedStreamid & 0x01) != 0x00) { + throw new ProtocolException("Received PUSH_PROMISE for stream " + promisedStreamid); + } + int streamId = pp.streamid(); + if ((streamId & 0x01) != 0x01) { + throw new ProtocolException("Received PUSH_PROMISE on stream " + streamId); + } // always decode the headers as they may affect connection-level HPACK // decoding state assert pushContinuationState == null; - HeaderDecoder decoder = new HeaderDecoder(); - decodeHeaders(pp, decoder::onDecoded); - int promisedStreamid = pp.getPromisedStream(); + PushPromiseDecoder decoder = new PushPromiseDecoder(streamId, promisedStreamid, parent); + decodeHeaders(pp, decoder); if (pp.endHeaders()) { - completePushPromise(promisedStreamid, parent, decoder.headers()); + if (decoder.errorRef.get() == null) { + completePushPromise(promisedStreamid, parent, decoder.headers()); + } } else { pushContinuationState = new PushContinuationState(decoder, pp); } } - private void handlePushContinuation(Stream parent, ContinuationFrame cf) + private void handlePushContinuation(PushContinuationState pcs, Stream parent, ContinuationFrame cf) throws IOException { - var pcs = pushContinuationState; - decodeHeaders(cf, pcs.pushContDecoder::onDecoded); + assert pcs.pushContFrame.streamid() == cf.streamid() : String.format( + "Received CONTINUATION on a different stream %s != %s", + cf.streamid(), pcs.pushContFrame.streamid()); + decodeHeaders(cf, pcs.pushContDecoder); // if all continuations are sent, set pushWithContinuation to null if (cf.endHeaders()) { - completePushPromise(pcs.pushContFrame.getPromisedStream(), parent, - pcs.pushContDecoder.headers()); + if (pcs.pushContDecoder.errorRef.get() == null) { + completePushPromise(pcs.pushContFrame.getPromisedStream(), parent, + pcs.pushContDecoder.headers()); + } pushContinuationState = null; } } private void completePushPromise(int promisedStreamid, Stream parent, HttpHeaders headers) throws IOException { + if (parent == null) { + resetStream(promisedStreamid, ResetFrame.REFUSED_STREAM); + return; + } HttpRequestImpl parentReq = parent.request; + if (promisedStreamid < nextPushStream) { + // From RFC 9113 section 5.1.1: + // The identifier of a newly established stream MUST be numerically + // greater than all streams that the initiating endpoint has + // opened or reserved. + protocolError(ResetFrame.PROTOCOL_ERROR, String.format( + "Unexpected stream identifier: %s < %s", promisedStreamid, nextPushStream)); + return; + } if (promisedStreamid != nextPushStream) { + // we don't support skipping stream ids; resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR); return; - } else if (!reserveStream(false)) { + } else if (!reserveStream(false, true)) { resetStream(promisedStreamid, ResetFrame.REFUSED_STREAM); return; } else { @@ -1163,11 +1315,17 @@ private void protocolError(int errorCode) private void protocolError(int errorCode, String msg) throws IOException { + String protocolError = "protocol error" + (msg == null?"":(": " + msg)); + ProtocolException protocolException = + new ProtocolException(protocolError); if (markHalfClosedLocal()) { + framesDecoder.close(protocolError); + subscriber.stop(protocolException); + if (debug.on()) debug.log("Sending GOAWAY due to " + protocolException); GoAwayFrame frame = new GoAwayFrame(0, errorCode); sendFrame(frame); } - shutdown(new IOException("protocol error" + (msg == null?"":(": " + msg)))); + shutdown(protocolException); } private void handleSettings(SettingsFrame frame) @@ -1309,7 +1467,7 @@ final Stream createStream(Exchange exchange) { Stream.PushedStream createPushStream(Stream parent, Exchange pushEx) { PushGroup pg = parent.exchange.getPushGroup(); - return new Stream.PushedStream<>(pg, this, pushEx); + return new Stream.PushedStream<>(parent, pg, this, pushEx); } /** @@ -1419,16 +1577,18 @@ private ByteBuffer getHeaderBuffer(int size) { private List encodeHeadersImpl(int bufferSize, HttpHeaders... headers) { ByteBuffer buffer = getHeaderBuffer(bufferSize); List buffers = new ArrayList<>(); - for(HttpHeaders header : headers) { + for (HttpHeaders header : headers) { for (Map.Entry> e : header.map().entrySet()) { String lKey = e.getKey().toLowerCase(Locale.US); List values = e.getValue(); for (String value : values) { hpackOut.header(lKey, value); while (!hpackOut.encode(buffer)) { - buffer.flip(); - buffers.add(buffer); - buffer = getHeaderBuffer(bufferSize); + if (!buffer.hasRemaining()) { + buffer.flip(); + buffers.add(buffer); + buffer = getHeaderBuffer(bufferSize); + } } } } @@ -1467,7 +1627,7 @@ private Stream registerNewStream(OutgoingHeaders> oh) { Throwable cause = null; synchronized (this) { if (isMarked(closedState, SHUTDOWN_REQUESTED)) { - cause = this.cause; + cause = this.cause.get(); if (cause == null) { cause = new IOException("Connection closed"); } @@ -1505,6 +1665,8 @@ void sendFrame(Http2Frame frame) { Stream stream = registerNewStream(oh); // provide protection from inserting unordered frames between Headers and Continuation if (stream != null) { + // we are creating a new stream: reset orphaned header count + orphanedHeaders.set(0); publisher.enqueue(encodeHeaders(oh, stream)); } } else { @@ -1573,7 +1735,7 @@ final class Http2TubeSubscriber implements TubeSubscriber { private volatile Flow.Subscription subscription; private volatile boolean completed; private volatile boolean dropped; - private volatile Throwable error; + private final AtomicReference errorRef = new AtomicReference<>(); private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); private final SequentialScheduler scheduler = @@ -1594,10 +1756,9 @@ final void processQueue() { asyncReceive(buffer); } } catch (Throwable t) { - Throwable x = error; - if (x == null) error = t; + errorRef.compareAndSet(null, t); } finally { - Throwable x = error; + Throwable x = errorRef.get(); if (x != null) { if (debug.on()) debug.log("Stopping scheduler", x); scheduler.stop(); @@ -1632,6 +1793,7 @@ public void onSubscribe(Flow.Subscription subscription) { @Override public void onNext(List item) { + if (completed) return; if (debug.on()) debug.log(() -> "onNext: got " + Utils.remaining(item) + " bytes in " + item.size() + " buffers"); queue.addAll(item); @@ -1640,19 +1802,21 @@ public void onNext(List item) { @Override public void onError(Throwable throwable) { + if (completed) return; if (debug.on()) debug.log(() -> "onError: " + throwable); - error = throwable; + errorRef.compareAndSet(null, throwable); completed = true; runOrSchedule(); } @Override public void onComplete() { + if (completed) return; String msg = isActive() ? "EOF reached while reading" : "Idle connection closed by HTTP/2 peer"; if (debug.on()) debug.log(msg); - error = new EOFException(msg); + errorRef.compareAndSet(null, new EOFException(msg)); completed = true; runOrSchedule(); } @@ -1664,6 +1828,18 @@ public void dropSubscription() { // then we might not need the 'dropped' boolean? dropped = true; } + + void stop(Throwable error) { + if (errorRef.compareAndSet(null, error)) { + completed = true; + scheduler.stop(); + queue.clear(); + if (subscription != null) { + subscription.cancel(); + } + queue.clear(); + } + } } boolean isActive() { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java index 3230fa665dc..eea88945610 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java @@ -959,7 +959,9 @@ private void debugCompleted(String tag, long startNanos, HttpRequest req) { // SSLException throw new SSLException(msg, throwable); } else if (throwable instanceof ProtocolException) { - throw new ProtocolException(msg); + ProtocolException pe = new ProtocolException(msg); + pe.initCause(throwable); + throw pe; } else if (throwable instanceof IOException) { throw new IOException(msg, throwable); } else { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java index 82425127f58..bec1b8b0298 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestImpl.java @@ -286,10 +286,10 @@ public HttpHeaders headers() { InetSocketAddress authority() { return authority; } - void setH2Upgrade(Http2ClientImpl h2client) { + void setH2Upgrade(Exchange exchange) { systemHeadersBuilder.setHeader("Connection", "Upgrade, HTTP2-Settings"); systemHeadersBuilder.setHeader("Upgrade", "h2c"); - systemHeadersBuilder.setHeader("HTTP2-Settings", h2client.getSettingsString()); + systemHeadersBuilder.setHeader("HTTP2-Settings", exchange.h2cSettingsStrings()); } @Override diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java index 66d89ae1fc5..22e03238d21 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/ResponseBodyHandlers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,6 +37,7 @@ import java.security.AccessControlContext; import java.security.AccessController; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; @@ -137,16 +138,21 @@ public void applyPushPromise( if (!initiatingURI.getHost().equalsIgnoreCase(pushRequestURI.getHost())) return; + String initiatingScheme = initiatingURI.getScheme(); + String pushRequestScheme = pushRequestURI.getScheme(); + + if (!initiatingScheme.equalsIgnoreCase(pushRequestScheme)) return; + int initiatingPort = initiatingURI.getPort(); if (initiatingPort == -1 ) { - if ("https".equalsIgnoreCase(initiatingURI.getScheme())) + if ("https".equalsIgnoreCase(initiatingScheme)) initiatingPort = 443; else initiatingPort = 80; } int pushPort = pushRequestURI.getPort(); if (pushPort == -1 ) { - if ("https".equalsIgnoreCase(pushRequestURI.getScheme())) + if ("https".equalsIgnoreCase(pushRequestScheme)) pushPort = 443; else pushPort = 80; diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java index f6447ade1e3..d55a9a4b446 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/Stream.java @@ -30,6 +30,7 @@ import java.io.UncheckedIOException; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; +import java.net.ProtocolException; import java.net.URI; import java.net.http.HttpResponse.BodyHandler; import java.net.http.HttpResponse.ResponseInfo; @@ -43,6 +44,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Flow; import java.util.concurrent.Flow.Subscription; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -52,10 +54,13 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.net.http.HttpResponse.BodySubscriber; + import jdk.internal.net.http.common.*; import jdk.internal.net.http.frame.*; import jdk.internal.net.http.hpack.DecodingCallback; +import static jdk.internal.net.http.Exchange.MAX_NON_FINAL_RESPONSES; + /** * Http/2 Stream handling. * @@ -140,6 +145,9 @@ class Stream extends ExchangeImpl { private volatile boolean closed; private volatile boolean endStreamSent; private volatile boolean finalResponseCodeReceived; + private volatile boolean trailerReceived; + private AtomicInteger nonFinalResponseCount = new AtomicInteger(); + // Indicates the first reason that was invoked when sending a ResetFrame // to the server. A streamState of 0 indicates that no reset was sent. // (see markStream(int code) @@ -508,16 +516,38 @@ void otherFrame(Http2Frame frame) throws IOException { // The Hpack decoder decodes into one of these consumers of name,value pairs DecodingCallback rspHeadersConsumer() { - return rspHeadersConsumer::onDecoded; + return rspHeadersConsumer; + } + + String checkInterimResponseCountExceeded() { + // this is also checked by Exchange - but tracking it here too provides + // a more informative message. + int count = nonFinalResponseCount.incrementAndGet(); + if (MAX_NON_FINAL_RESPONSES > 0 && (count < 0 || count > MAX_NON_FINAL_RESPONSES)) { + return String.format( + "Stream %s PROTOCOL_ERROR: too many interim responses received: %s > %s", + streamid, count, MAX_NON_FINAL_RESPONSES); + } + return null; } protected void handleResponse(HeaderFrame hf) throws IOException { HttpHeaders responseHeaders = responseHeadersBuilder.build(); if (!finalResponseCodeReceived) { - responseCode = (int) responseHeaders - .firstValueAsLong(":status") - .orElseThrow(() -> new IOException("no statuscode in response")); + try { + responseCode = (int) responseHeaders + .firstValueAsLong(":status") + .orElseThrow(() -> new ProtocolException(String.format( + "Stream %s PROTOCOL_ERROR: no status code in response", + streamid))); + } catch (ProtocolException cause) { + cancelImpl(cause, ResetFrame.PROTOCOL_ERROR); + rspHeadersConsumer.reset(); + return; + } + + String protocolErrorMsg = null; // If informational code, response is partially complete if (responseCode < 100 || responseCode > 199) { this.finalResponseCodeReceived = true; @@ -525,23 +555,31 @@ protected void handleResponse(HeaderFrame hf) throws IOException { // see RFC 9113 section 8.1: // A HEADERS frame with the END_STREAM flag set that carries an // informational status code is malformed - String msg = ("Stream %s PROTOCOL_ERROR: " + - "HEADERS frame with status %s has END_STREAM flag set") - .formatted(streamid, responseCode); + protocolErrorMsg = String.format( + "Stream %s PROTOCOL_ERROR: " + + "HEADERS frame with status %s has END_STREAM flag set", + streamid, responseCode); + } else { + protocolErrorMsg = checkInterimResponseCountExceeded(); + } + + if (protocolErrorMsg != null) { if (debug.on()) { - debug.log(msg); + debug.log(protocolErrorMsg); } - cancelImpl(new IOException(msg), ResetFrame.PROTOCOL_ERROR); + cancelImpl(new ProtocolException(protocolErrorMsg), ResetFrame.PROTOCOL_ERROR); + rspHeadersConsumer.reset(); + return; } response = new Response( request, exchange, responseHeaders, connection(), responseCode, HttpClient.Version.HTTP_2); - /* TODO: review if needs to be removed - the value is not used, but in case `content-length` doesn't parse as - long, there will be NumberFormatException. If left as is, make sure - code up the stack handles NFE correctly. */ + /* TODO: review if needs to be removed + the value is not used, but in case `content-length` doesn't parse as + long, there will be NumberFormatException. If left as is, make sure + code up the stack handles NFE correctly. */ responseHeaders.firstValueAsLong("content-length"); if (Log.headers()) { @@ -560,6 +598,15 @@ request, exchange, responseHeaders, connection(), Log.dumpHeaders(sb, " ", responseHeaders); Log.logHeaders(sb.toString()); } + if (trailerReceived) { + String protocolErrorMsg = String.format( + "Stream %s PROTOCOL_ERROR: trailers already received", streamid); + if (debug.on()) { + debug.log(protocolErrorMsg); + } + cancelImpl(new ProtocolException(protocolErrorMsg), ResetFrame.PROTOCOL_ERROR); + } + trailerReceived = true; rspHeadersConsumer.reset(); } @@ -1147,7 +1194,7 @@ private DataFrame getEmptyEndStreamDataFrame() { /** * A List of responses relating to this stream. Normally there is only - * one response, but intermediate responses like 100 are allowed + * one response, but interim responses like 100 are allowed * and must be passed up to higher level before continuing. Deals with races * such as if responses are returned before the CFs get created by * getResponseAsync() @@ -1352,7 +1399,7 @@ void cancelImpl(Throwable e) { cancelImpl(e, ResetFrame.CANCEL); } - private void cancelImpl(final Throwable e, final int resetFrameErrCode) { + void cancelImpl(final Throwable e, final int resetFrameErrCode) { errorRef.compareAndSet(null, e); if (debug.on()) { if (streamid == 0) debug.log("cancelling stream: %s", (Object)e); @@ -1455,6 +1502,7 @@ void close() { } static class PushedStream extends Stream { + final Stream parent; final PushGroup pushGroup; // push streams need the response CF allocated up front as it is // given directly to user via the multi handler callback function. @@ -1464,16 +1512,17 @@ static class PushedStream extends Stream { volatile HttpResponse.BodyHandler pushHandler; private volatile boolean finalPushResponseCodeReceived; - PushedStream(PushGroup pushGroup, + PushedStream(Stream parent, + PushGroup pushGroup, Http2Connection connection, Exchange pushReq) { // ## no request body possible, null window controller super(connection, pushReq, null); + this.parent = parent; this.pushGroup = pushGroup; this.pushReq = pushReq.request(); this.pushCF = new MinimalFuture<>(); this.responseCF = new MinimalFuture<>(); - } CompletableFuture> responseCF() { @@ -1561,7 +1610,16 @@ protected void handleResponse(HeaderFrame hf) { .orElse(-1); if (responseCode == -1) { - completeResponseExceptionally(new IOException("No status code")); + cancelImpl(new ProtocolException("No status code"), ResetFrame.PROTOCOL_ERROR); + rspHeadersConsumer.reset(); + return; + } else if (responseCode >= 100 && responseCode < 200) { + String protocolErrorMsg = checkInterimResponseCountExceeded(); + if (protocolErrorMsg != null) { + cancelImpl(new ProtocolException(protocolErrorMsg), ResetFrame.PROTOCOL_ERROR); + rspHeadersConsumer.reset(); + return; + } } this.finalPushResponseCodeReceived = true; @@ -1643,7 +1701,9 @@ final String dbgString() { return connection.dbgString() + "/Stream("+streamid+")"; } - private class HeadersConsumer extends ValidatingHeadersConsumer { + private class HeadersConsumer extends ValidatingHeadersConsumer implements DecodingCallback { + + boolean maxHeaderListSizeReached; @Override public void reset() { @@ -1656,6 +1716,9 @@ public void reset() { public void onDecoded(CharSequence name, CharSequence value) throws UncheckedIOException { + if (maxHeaderListSizeReached) { + return; + } try { String n = name.toString(); String v = value.toString(); @@ -1678,6 +1741,23 @@ public void onDecoded(CharSequence name, CharSequence value) protected String formatMessage(String message, String header) { return "malformed response: " + super.formatMessage(message, header); } + + @Override + public void onMaxHeaderListSizeReached(long size, int maxHeaderListSize) throws ProtocolException { + if (maxHeaderListSizeReached) return; + try { + DecodingCallback.super.onMaxHeaderListSizeReached(size, maxHeaderListSize); + } catch (ProtocolException cause) { + maxHeaderListSizeReached = true; + // If this is a push stream: cancel the parent. + if (Stream.this instanceof Stream.PushedStream ps) { + ps.parent.onProtocolError(cause); + } + // cancel the stream, continue processing + onProtocolError(cause); + reset(); + } + } } final class Http2StreamResponseSubscriber extends HttpBodySubscriberWrapper { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/HeaderDecoder.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/HeaderDecoder.java index 62d03844d2e..d81f52e6630 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/HeaderDecoder.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/HeaderDecoder.java @@ -39,7 +39,11 @@ public void onDecoded(CharSequence name, CharSequence value) { String n = name.toString(); String v = value.toString(); super.onDecoded(n, v); - headersBuilder.addHeader(n, v); + addHeader(n, v); + } + + protected void addHeader(String name, String value) { + headersBuilder.addHeader(name, value); } public HttpHeaders headers() { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java index 812b8c5374c..6a9aca39271 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/Utils.java @@ -579,6 +579,19 @@ public static int getIntegerProperty(String name, int defaultValue) { Integer.parseInt(System.getProperty(name, String.valueOf(defaultValue)))); } + public static int getIntegerNetProperty(String property, int min, int max, int defaultValue, boolean log) { + int value = Utils.getIntegerNetProperty(property, defaultValue); + // use default value if misconfigured + if (value < min || value > max) { + if (log && Log.errors()) { + Log.logError("Property value for {0}={1} not in [{2}..{3}]: " + + "using default={4}", property, value, min, max, defaultValue); + } + value = defaultValue; + } + return value; + } + public static SSLParameters copySSLParameters(SSLParameters p) { SSLParameters p1 = new SSLParameters(); p1.setAlgorithmConstraints(p.getAlgorithmConstraints()); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Decoder.java b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Decoder.java index 9d57a734ac3..881be12c67c 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Decoder.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Decoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,7 @@ import jdk.internal.net.http.hpack.HPACK.Logger; import java.io.IOException; +import java.net.ProtocolException; import java.nio.ByteBuffer; import java.util.List; import java.util.concurrent.atomic.AtomicLong; @@ -107,12 +108,16 @@ public final class Decoder { private final StringReader stringReader; private final StringBuilder name; private final StringBuilder value; + private final int maxHeaderListSize; + private final int maxIndexed; private int intValue; private boolean firstValueRead; private boolean firstValueIndex; private boolean nameHuffmanEncoded; private boolean valueHuffmanEncoded; private int capacity; + private long size; + private int indexed; /** * Constructs a {@code Decoder} with the specified initial capacity of the @@ -129,6 +134,31 @@ public final class Decoder { * if capacity is negative */ public Decoder(int capacity) { + this(capacity, 0, 0); + } + + /** + * Constructs a {@code Decoder} with the specified initial capacity of the + * header table, a max header list size, and a maximum number of literals + * with indexing per header section. + * + *

    The value of the capacity has to be agreed between decoder and encoder out-of-band, + * e.g. by a protocol that uses HPACK + * (see 4.2. Maximum Table Size). + * + * @param capacity + * a non-negative integer + * @param maxHeaderListSize + * a maximum value for the header list size. This is the uncompressed + * names size + uncompressed values size + 32 bytes per field line + * @param maxIndexed + * the maximum number of literal with indexing we're prepared to handle + * for a header field section + * + * @throws IllegalArgumentException + * if capacity is negative + */ + public Decoder(int capacity, int maxHeaderListSize, int maxIndexed) { id = DECODERS_IDS.incrementAndGet(); logger = HPACK.getLogger().subLogger("Decoder#" + id); if (logger.isLoggable(NORMAL)) { @@ -145,6 +175,8 @@ public Decoder(int capacity) { toString(), hashCode); }); } + this.maxHeaderListSize = maxHeaderListSize; + this.maxIndexed = maxIndexed; setMaxCapacity0(capacity); table = new SimpleHeaderTable(capacity, logger.subLogger("HeaderTable")); integerReader = new IntegerReader(); @@ -242,22 +274,25 @@ public void decode(ByteBuffer headerBlock, requireNonNull(consumer, "consumer"); if (logger.isLoggable(NORMAL)) { logger.log(NORMAL, () -> format("reading %s, end of header block? %s", - headerBlock, endOfHeaderBlock)); + headerBlock, endOfHeaderBlock)); } while (headerBlock.hasRemaining()) { proceed(headerBlock, consumer); } if (endOfHeaderBlock && state != State.READY) { logger.log(NORMAL, () -> format("unexpected end of %s representation", - state)); + state)); throw new IOException("Unexpected end of header block"); } + if (endOfHeaderBlock) { + size = indexed = 0; + } } private void proceed(ByteBuffer input, DecodingCallback action) throws IOException { switch (state) { - case READY -> resumeReady(input); + case READY -> resumeReady(input, action); case INDEXED -> resumeIndexed(input, action); case LITERAL -> resumeLiteral(input, action); case LITERAL_WITH_INDEXING -> resumeLiteralWithIndexing(input, action); @@ -268,7 +303,7 @@ private void proceed(ByteBuffer input, DecodingCallback action) } } - private void resumeReady(ByteBuffer input) { + private void resumeReady(ByteBuffer input, DecodingCallback action) throws IOException { int b = input.get(input.position()) & 0xff; // absolute read State s = states.get(b); if (logger.isLoggable(EXTRA)) { @@ -289,6 +324,9 @@ private void resumeReady(ByteBuffer input) { } break; case LITERAL_WITH_INDEXING: + if (maxIndexed > 0 && ++indexed > maxIndexed) { + action.onMaxLiteralWithIndexingReached(indexed, maxIndexed); + } state = State.LITERAL_WITH_INDEXING; firstValueIndex = (b & 0b0011_1111) != 0; if (firstValueIndex) { @@ -315,6 +353,12 @@ private void resumeReady(ByteBuffer input) { } } + private void checkMaxHeaderListSize(long sz, DecodingCallback consumer) throws ProtocolException { + if (maxHeaderListSize > 0 && sz > maxHeaderListSize) { + consumer.onMaxHeaderListSizeReached(sz, maxHeaderListSize); + } + } + // 0 1 2 3 4 5 6 7 // +---+---+---+---+---+---+---+---+ // | 1 | Index (7+) | @@ -332,6 +376,8 @@ private void resumeIndexed(ByteBuffer input, DecodingCallback action) } try { SimpleHeaderTable.HeaderField f = getHeaderFieldAt(intValue); + size = size + 32 + f.name.length() + f.value.length(); + checkMaxHeaderListSize(size, action); action.onIndexed(intValue, f.name, f.value); } finally { state = State.READY; @@ -374,7 +420,7 @@ private SimpleHeaderTable.HeaderField getHeaderFieldAt(int index) // private void resumeLiteral(ByteBuffer input, DecodingCallback action) throws IOException { - if (!completeReading(input)) { + if (!completeReading(input, action)) { return; } try { @@ -385,6 +431,8 @@ private void resumeLiteral(ByteBuffer input, DecodingCallback action) intValue, value, valueHuffmanEncoded)); } SimpleHeaderTable.HeaderField f = getHeaderFieldAt(intValue); + size = size + 32 + f.name.length() + value.length(); + checkMaxHeaderListSize(size, action); action.onLiteral(intValue, f.name, value, valueHuffmanEncoded); } else { if (logger.isLoggable(NORMAL)) { @@ -392,6 +440,8 @@ private void resumeLiteral(ByteBuffer input, DecodingCallback action) "literal without indexing ('%s', huffman=%b, '%s', huffman=%b)", name, nameHuffmanEncoded, value, valueHuffmanEncoded)); } + size = size + 32 + name.length() + value.length(); + checkMaxHeaderListSize(size, action); action.onLiteral(name, nameHuffmanEncoded, value, valueHuffmanEncoded); } } finally { @@ -425,7 +475,7 @@ private void resumeLiteral(ByteBuffer input, DecodingCallback action) private void resumeLiteralWithIndexing(ByteBuffer input, DecodingCallback action) throws IOException { - if (!completeReading(input)) { + if (!completeReading(input, action)) { return; } try { @@ -445,6 +495,8 @@ private void resumeLiteralWithIndexing(ByteBuffer input, } SimpleHeaderTable.HeaderField f = getHeaderFieldAt(intValue); n = f.name; + size = size + 32 + n.length() + v.length(); + checkMaxHeaderListSize(size, action); action.onLiteralWithIndexing(intValue, n, v, valueHuffmanEncoded); } else { n = name.toString(); @@ -453,6 +505,8 @@ private void resumeLiteralWithIndexing(ByteBuffer input, "literal with incremental indexing ('%s', huffman=%b, '%s', huffman=%b)", n, nameHuffmanEncoded, value, valueHuffmanEncoded)); } + size = size + 32 + n.length() + v.length(); + checkMaxHeaderListSize(size, action); action.onLiteralWithIndexing(n, nameHuffmanEncoded, v, valueHuffmanEncoded); } table.put(n, v); @@ -486,7 +540,7 @@ private void resumeLiteralWithIndexing(ByteBuffer input, private void resumeLiteralNeverIndexed(ByteBuffer input, DecodingCallback action) throws IOException { - if (!completeReading(input)) { + if (!completeReading(input, action)) { return; } try { @@ -497,6 +551,8 @@ private void resumeLiteralNeverIndexed(ByteBuffer input, intValue, value, valueHuffmanEncoded)); } SimpleHeaderTable.HeaderField f = getHeaderFieldAt(intValue); + size = size + 32 + f.name.length() + value.length(); + checkMaxHeaderListSize(size, action); action.onLiteralNeverIndexed(intValue, f.name, value, valueHuffmanEncoded); } else { if (logger.isLoggable(NORMAL)) { @@ -504,6 +560,8 @@ private void resumeLiteralNeverIndexed(ByteBuffer input, "literal never indexed ('%s', huffman=%b, '%s', huffman=%b)", name, nameHuffmanEncoded, value, valueHuffmanEncoded)); } + size = size + 32 + name.length() + value.length(); + checkMaxHeaderListSize(size, action); action.onLiteralNeverIndexed(name, nameHuffmanEncoded, value, valueHuffmanEncoded); } } finally { @@ -541,7 +599,7 @@ private void resumeSizeUpdate(ByteBuffer input, } } - private boolean completeReading(ByteBuffer input) throws IOException { + private boolean completeReading(ByteBuffer input, DecodingCallback action) throws IOException { if (!firstValueRead) { if (firstValueIndex) { if (!integerReader.read(input)) { @@ -551,6 +609,8 @@ private boolean completeReading(ByteBuffer input) throws IOException { integerReader.reset(); } else { if (!stringReader.read(input, name)) { + long sz = size + 32 + name.length(); + checkMaxHeaderListSize(sz, action); return false; } nameHuffmanEncoded = stringReader.isHuffmanEncoded(); @@ -560,6 +620,8 @@ private boolean completeReading(ByteBuffer input) throws IOException { return false; } else { if (!stringReader.read(input, value)) { + long sz = size + 32 + name.length() + value.length(); + checkMaxHeaderListSize(sz, action); return false; } } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/hpack/DecodingCallback.java b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/DecodingCallback.java index 5e9df860feb..228f9bf0206 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/hpack/DecodingCallback.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/DecodingCallback.java @@ -24,6 +24,7 @@ */ package jdk.internal.net.http.hpack; +import java.net.ProtocolException; import java.nio.ByteBuffer; /** @@ -292,4 +293,17 @@ default void onLiteralWithIndexing(CharSequence name, * new capacity of the header table */ default void onSizeUpdate(int capacity) { } + + default void onMaxHeaderListSizeReached(long size, int maxHeaderListSize) + throws ProtocolException { + throw new ProtocolException(String + .format("Size exceeds MAX_HEADERS_LIST_SIZE: %s > %s", + size, maxHeaderListSize)); + } + + default void onMaxLiteralWithIndexingReached(long indexed, int maxIndexed) + throws ProtocolException { + throw new ProtocolException(String.format("Too many literal with indexing: %s > %s", + indexed, maxIndexed)); + } } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Encoder.java b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Encoder.java index 4188937b1ad..c603e917ca4 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Encoder.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/hpack/Encoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -258,9 +258,10 @@ public void header(CharSequence name, } } } + assert encoding : "encoding is false"; } - private boolean isHuffmanBetterFor(CharSequence value) { + protected final boolean isHuffmanBetterFor(CharSequence value) { // prefer Huffman encoding only if it is strictly smaller than Latin-1 return huffmanWriter.lengthOf(value) < value.length(); } @@ -340,6 +341,10 @@ protected int calculateCapacity(int maxCapacity) { return 0; } + protected final int tableIndexOf(CharSequence name, CharSequence value) { + return getHeaderTable().indexOf(name, value); + } + /** * Encodes the {@linkplain #header(CharSequence, CharSequence) set up} * header into the given buffer. @@ -380,6 +385,7 @@ public final boolean encode(ByteBuffer headerBlock) { writer.reset(); // FIXME: WHY? encoding = false; } + assert done || encoding : "done: " + done + ", encoding: " + encoding; return done; } @@ -542,4 +548,8 @@ protected final void checkEncoding() { // TODO: better name e.g. checkIfEncoding "Previous encoding operation hasn't finished yet"); } } + + protected final Logger logger() { + return logger; + } } diff --git a/src/java.net.http/share/classes/module-info.java b/src/java.net.http/share/classes/module-info.java index cf9d07bdf32..5303e818866 100644 --- a/src/java.net.http/share/classes/module-info.java +++ b/src/java.net.http/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -115,6 +115,25 @@ * The HTTP/2 client maximum frame size in bytes. The server is not permitted to send a frame * larger than this. *

  • + *
  • {@systemProperty jdk.httpclient.maxLiteralWithIndexing} (default: 512)
    + * The maximum number of header field lines (header name and value pairs) that a + * client is willing to add to the HPack Decoder dynamic table during the decoding + * of an entire header field section. + * This is purely an implementation limit. + * If a peer sends a field section with encoding that + * exceeds this limit a {@link java.net.ProtocolException ProtocolException} will be raised. + * A value of zero or a negative value means no limit. + *

  • + *
  • {@systemProperty jdk.httpclient.maxNonFinalResponses} (default: 8)
    + * The maximum number of interim (non-final) responses that a client is prepared + * to accept on a request-response stream before the final response is received. + * Interim responses are responses with a status in the range [100, 199] inclusive. + * This is purely an implementation limit. + * If a peer sends a number of interim response that exceeds this limit before + * sending the final response, a {@link java.net.ProtocolException ProtocolException} + * will be raised. + * A value of zero or a negative value means no limit. + *

  • *
  • {@systemProperty jdk.httpclient.maxstreams} (default: 100)
    * The maximum number of HTTP/2 push streams that the client will permit servers to open * simultaneously. @@ -155,6 +174,15 @@ * conf/net.properties)
    A comma separated list of HTTP authentication scheme names, that * are disallowed for use by the HTTP client implementation, for HTTP CONNECT tunneling. *

  • + *
  • {@systemProperty jdk.http.maxHeaderSize} (default: 393216 or 384kB) + *
    The maximum header field section size that the client is prepared to accept. + * This is computed as the sum of the size of the uncompressed header name, plus + * the size of the uncompressed header value, plus an overhead of 32 bytes for + * each field section line. If a peer sends a field section that exceeds this + * size a {@link java.net.ProtocolException ProtocolException} will be raised. + * This applies to all versions of the protocol. A value of zero or a negative + * value means no limit. + *

  • * * @moduleGraph * @since 11 diff --git a/src/jdk.httpserver/share/classes/module-info.java b/src/jdk.httpserver/share/classes/module-info.java index 46dbb6ea64f..23e04405ee8 100644 --- a/src/jdk.httpserver/share/classes/module-info.java +++ b/src/jdk.httpserver/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -70,6 +70,16 @@ * while the headers are being read, then the connection is terminated and the request ignored. * If the value is less than or equal to zero, then the default value is used. * + *
  • {@systemProperty sun.net.httpserver.maxReqHeaderSize} (default: 393216 or 384kB)
    + * The maximum header field section size that the server is prepared to accept. + * This is computed as the sum of the size of the header name, plus + * the size of the header value, plus an overhead of 32 bytes for + * each field section line. The request line counts as a first field section line, + * where the name is empty and the value is the whole line. + * If this limit is exceeded while the headers are being read, then the connection + * is terminated and the request ignored. + * If the value is less than or equal to zero, there is no limit. + *

  • *
  • {@systemProperty sun.net.httpserver.maxReqTime} (default: -1)
    * The maximum time in milliseconds allowed to receive a request headers and body. * In practice, the actual time is a function of request size, network speed, and handler diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java index 99dac0547ce..6c0c2c271ed 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,8 +44,10 @@ class Request { private SocketChannel chan; private InputStream is; private OutputStream os; + private final int maxReqHeaderSize; Request (InputStream rawInputStream, OutputStream rawout) throws IOException { + this.maxReqHeaderSize = ServerConfig.getMaxReqHeaderSize(); is = rawInputStream; os = rawout; do { @@ -75,6 +77,7 @@ public OutputStream outputStream () { public String readLine () throws IOException { boolean gotCR = false, gotLF = false; pos = 0; lineBuf = new StringBuffer(); + long lsize = 32; while (!gotLF) { int c = is.read(); if (c == -1) { @@ -87,20 +90,27 @@ public String readLine () throws IOException { gotCR = false; consume (CR); consume (c); + lsize = lsize + 2; } } else { if (c == CR) { gotCR = true; } else { consume (c); + lsize = lsize + 1; } } + if (maxReqHeaderSize > 0 && lsize > maxReqHeaderSize) { + throw new IOException("Maximum header (" + + "sun.net.httpserver.maxReqHeaderSize) exceeded, " + + ServerConfig.getMaxReqHeaderSize() + "."); + } } lineBuf.append (buf, 0, pos); return new String (lineBuf); } - private void consume (int c) { + private void consume (int c) throws IOException { if (pos == BUF_LEN) { lineBuf.append (buf); pos = 0; @@ -138,13 +148,22 @@ Headers headers () throws IOException { len = 1; firstc = c; } + long hsize = startLine.length() + 32L; while (firstc != LF && firstc != CR && firstc >= 0) { int keyend = -1; int c; boolean inKey = firstc > ' '; s[len++] = (char) firstc; + hsize = hsize + 1; parseloop:{ + // We start parsing for a new name value pair here. + // The max header size includes an overhead of 32 bytes per + // name value pair. + // See SETTINGS_MAX_HEADER_LIST_SIZE, RFC 9113, section 6.5.2. + long maxRemaining = maxReqHeaderSize > 0 + ? maxReqHeaderSize - hsize - 32 + : Long.MAX_VALUE; while ((c = is.read()) >= 0) { switch (c) { /*fallthrough*/ @@ -178,6 +197,11 @@ Headers headers () throws IOException { s = ns; } s[len++] = (char) c; + if (maxReqHeaderSize > 0 && len > maxRemaining) { + throw new IOException("Maximum header (" + + "sun.net.httpserver.maxReqHeaderSize) exceeded, " + + ServerConfig.getMaxReqHeaderSize() + "."); + } } firstc = -1; } @@ -205,6 +229,13 @@ Headers headers () throws IOException { "sun.net.httpserver.maxReqHeaders) exceeded, " + ServerConfig.getMaxReqHeaders() + "."); } + hsize = hsize + len + 32; + if (maxReqHeaderSize > 0 && hsize > maxReqHeaderSize) { + throw new IOException("Maximum header (" + + "sun.net.httpserver.maxReqHeaderSize) exceeded, " + + ServerConfig.getMaxReqHeaderSize() + "."); + } + if (k == null) { // Headers disallows null keys, use empty string k = ""; // instead to represent invalid key } diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java index 21f6165b05e..9186dd4c168 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,6 +49,7 @@ class ServerConfig { // timing out request/response if max request/response time is configured private static final long DEFAULT_REQ_RSP_TIMER_TASK_SCHEDULE_MILLIS = 1000; private static final int DEFAULT_MAX_REQ_HEADERS = 200; + private static final int DEFAULT_MAX_REQ_HEADER_SIZE = 380 * 1024; private static final long DEFAULT_DRAIN_AMOUNT = 64 * 1024; private static long idleTimerScheduleMillis; @@ -62,6 +63,9 @@ class ServerConfig { private static int maxIdleConnections; // The maximum number of request headers allowable private static int maxReqHeaders; + // a maximum value for the header list size. This is the + // names size + values size + 32 bytes per field line + private static int maxReqHeadersSize; // max time a request or response is allowed to take private static long maxReqTime; private static long maxRspTime; @@ -107,6 +111,14 @@ public Void run () { maxReqHeaders = DEFAULT_MAX_REQ_HEADERS; } + // a value <= 0 means unlimited + maxReqHeadersSize = Integer.getInteger( + "sun.net.httpserver.maxReqHeaderSize", + DEFAULT_MAX_REQ_HEADER_SIZE); + if (maxReqHeadersSize <= 0) { + maxReqHeadersSize = 0; + } + maxReqTime = Long.getLong("sun.net.httpserver.maxReqTime", DEFAULT_MAX_REQ_TIME); @@ -215,6 +227,10 @@ static int getMaxReqHeaders() { return maxReqHeaders; } + static int getMaxReqHeaderSize() { + return maxReqHeadersSize; + } + /** * @return Returns the maximum amount of time the server will wait for the request to be read * completely. This method can return a value of 0 or negative to imply no maximum limit has diff --git a/test/jdk/java/net/httpclient/ShutdownNow.java b/test/jdk/java/net/httpclient/ShutdownNow.java index de2279277c7..3fa2f37615e 100644 --- a/test/jdk/java/net/httpclient/ShutdownNow.java +++ b/test/jdk/java/net/httpclient/ShutdownNow.java @@ -136,6 +136,7 @@ static boolean hasExpectedMessage(IOException io) { if (message.equals("shutdownNow")) return true; // exception from cancelling an HTTP/2 stream if (message.matches("Stream [0-9]+ cancelled")) return true; + if (message.contains("connection closed locally")) return true; return false; } diff --git a/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java b/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java index a2eb054d809..415331a92b4 100644 --- a/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java +++ b/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java @@ -49,6 +49,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.ProtocolException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpHeaders; @@ -192,7 +193,8 @@ public void testSendHeadersOnPushPromiseStream() throws Exception { client.sendAsync(hreq, HttpResponse.BodyHandlers.ofString(UTF_8), pph); CompletionException t = expectThrows(CompletionException.class, () -> cf.join()); - assertEquals(t.getCause().getClass(), IOException.class, "Expected an IOException but got " + t.getCause()); + assertEquals(t.getCause().getClass(), ProtocolException.class, + "Expected a ProtocolException but got " + t.getCause()); System.err.println("Client received the following expected exception: " + t.getCause()); faultyServer.stop(); } @@ -219,7 +221,10 @@ private void verify(HttpResponse resp) { static class Http2PushPromiseHeadersExchangeImpl extends Http2TestExchangeImpl { - Http2PushPromiseHeadersExchangeImpl(int streamid, String method, HttpHeaders reqheaders, HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is, SSLSession sslSession, BodyOutputStream os, Http2TestServerConnection conn, boolean pushAllowed) { + Http2PushPromiseHeadersExchangeImpl(int streamid, String method, HttpHeaders reqheaders, + HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is, + SSLSession sslSession, BodyOutputStream os, + Http2TestServerConnection conn, boolean pushAllowed) { super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession, os, conn, pushAllowed); } diff --git a/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/common/HttpServerAdapters.java b/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/common/HttpServerAdapters.java index e18dd87a507..98da7d5533f 100644 --- a/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/common/HttpServerAdapters.java +++ b/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/common/HttpServerAdapters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -239,6 +239,7 @@ public static abstract class HttpTestExchange implements AutoCloseable { public abstract String getRequestMethod(); public abstract void close(); public abstract InetSocketAddress getRemoteAddress(); + public abstract InetSocketAddress getLocalAddress(); public void serverPush(URI uri, HttpHeaders headers, byte[] body) { ByteArrayInputStream bais = new ByteArrayInputStream(body); serverPush(uri, headers, bais); @@ -301,7 +302,10 @@ void doFilter(Filter.Chain chain) throws IOException { public InetSocketAddress getRemoteAddress() { return exchange.getRemoteAddress(); } - + @Override + public InetSocketAddress getLocalAddress() { + return exchange.getLocalAddress(); + } @Override public URI getRequestURI() { return exchange.getRequestURI(); } @Override @@ -362,6 +366,10 @@ void doFilter(Filter.Chain filter) throws IOException { public InetSocketAddress getRemoteAddress() { return exchange.getRemoteAddress(); } + @Override + public InetSocketAddress getLocalAddress() { + return exchange.getLocalAddress(); + } @Override public URI getRequestURI() { return exchange.getRequestURI(); } diff --git a/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/HpackTestEncoder.java b/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/HpackTestEncoder.java new file mode 100644 index 00000000000..f54a4a766b8 --- /dev/null +++ b/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/HpackTestEncoder.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.httpclient.test.lib.http2; + +import java.util.function.*; + +import jdk.internal.net.http.hpack.Encoder; + +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; +import static jdk.internal.net.http.hpack.HPACK.Logger.Level.EXTRA; +import static jdk.internal.net.http.hpack.HPACK.Logger.Level.NORMAL; + +public class HpackTestEncoder extends Encoder { + + public HpackTestEncoder(int maxCapacity) { + super(maxCapacity); + } + + /** + * Sets up the given header {@code (name, value)} with possibly sensitive + * value. + * + *

    If the {@code value} is sensitive (think security, secrecy, etc.) + * this encoder will compress it using a special representation + * (see 6.2.3. Literal Header Field Never Indexed). + * + *

    Fixates {@code name} and {@code value} for the duration of encoding. + * + * @param name + * the name + * @param value + * the value + * @param sensitive + * whether or not the value is sensitive + * + * @throws NullPointerException + * if any of the arguments are {@code null} + * @throws IllegalStateException + * if the encoder hasn't fully encoded the previous header, or + * hasn't yet started to encode it + * @see #header(CharSequence, CharSequence) + * @see DecodingCallback#onDecoded(CharSequence, CharSequence, boolean) + */ + public void header(CharSequence name, + CharSequence value, + boolean sensitive) throws IllegalStateException { + if (sensitive || getMaxCapacity() == 0) { + super.header(name, value, true); + } else { + header(name, value, false, (n,v) -> false); + } + } + /** + * Sets up the given header {@code (name, value)} with possibly sensitive + * value. + * + *

    If the {@code value} is sensitive (think security, secrecy, etc.) + * this encoder will compress it using a special representation + * (see 6.2.3. Literal Header Field Never Indexed). + * + *

    Fixates {@code name} and {@code value} for the duration of encoding. + * + * @param name + * the name + * @param value + * the value + * @param insertionPolicy + * a bipredicate to indicate whether a name value pair + * should be added to the dynamic table + * + * @throws NullPointerException + * if any of the arguments are {@code null} + * @throws IllegalStateException + * if the encoder hasn't fully encoded the previous header, or + * hasn't yet started to encode it + * @see #header(CharSequence, CharSequence) + * @see DecodingCallback#onDecoded(CharSequence, CharSequence, boolean) + */ + public void header(CharSequence name, + CharSequence value, + BiPredicate insertionPolicy) + throws IllegalStateException { + header(name, value, false, insertionPolicy); + } + + /** + * Sets up the given header {@code (name, value)} with possibly sensitive + * value. + * + *

    If the {@code value} is sensitive (think security, secrecy, etc.) + * this encoder will compress it using a special representation + * (see + * 6.2.3. Literal Header Field Never Indexed). + * + *

    Fixates {@code name} and {@code value} for the duration of encoding. + * + * @param name + * the name + * @param value + * the value + * @param sensitive + * whether or not the value is sensitive + * @param insertionPolicy + * a bipredicate to indicate whether a name value pair + * should be added to the dynamic table + * + * @throws NullPointerException + * if any of the arguments are {@code null} + * @throws IllegalStateException + * if the encoder hasn't fully encoded the previous header, or + * hasn't yet started to encode it + * @see #header(CharSequence, CharSequence) + * @see DecodingCallback#onDecoded(CharSequence, CharSequence, boolean) + */ + public void header(CharSequence name, + CharSequence value, + boolean sensitive, + BiPredicate insertionPolicy) + throws IllegalStateException { + if (sensitive == true || getMaxCapacity() == 0 || !insertionPolicy.test(name, value)) { + super.header(name, value, sensitive); + return; + } + var logger = logger(); + // Arguably a good balance between complexity of implementation and + // efficiency of encoding + requireNonNull(name, "name"); + requireNonNull(value, "value"); + var t = getHeaderTable(); + int index = tableIndexOf(name, value); + if (logger.isLoggable(NORMAL)) { + logger.log(NORMAL, () -> format("encoding with indexing ('%s', '%s'): index:%s", + name, value, index)); + } + if (index > 0) { + indexed(index); + } else { + boolean huffmanValue = isHuffmanBetterFor(value); + if (index < 0) { + literalWithIndexing(-index, value, huffmanValue); + } else { + boolean huffmanName = isHuffmanBetterFor(name); + literalWithIndexing(name, huffmanName, value, huffmanValue); + } + } + } + + protected int calculateCapacity(int maxCapacity) { + return maxCapacity; + } + +} diff --git a/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestExchange.java b/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestExchange.java index 208a8d54e08..1a8b5d92af5 100644 --- a/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestExchange.java +++ b/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestExchange.java @@ -29,9 +29,12 @@ import java.net.URI; import java.net.InetSocketAddress; import java.net.http.HttpHeaders; +import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.function.BiPredicate; import javax.net.ssl.SSLSession; import jdk.internal.net.http.common.HttpHeadersBuilder; +import jdk.internal.net.http.frame.Http2Frame; public interface Http2TestExchange { @@ -53,6 +56,12 @@ public interface Http2TestExchange { void sendResponseHeaders(int rCode, long responseLength) throws IOException; + default void sendResponseHeaders(int rCode, long responseLength, + BiPredicate insertionPolicy) + throws IOException { + sendResponseHeaders(rCode, responseLength); + } + InetSocketAddress getRemoteAddress(); int getResponseCode(); @@ -65,6 +74,10 @@ public interface Http2TestExchange { void serverPush(URI uri, HttpHeaders headers, InputStream content); + default void sendFrames(List frames) throws IOException { + throw new UnsupportedOperationException("not implemented"); + } + /** * Send a PING on this exchanges connection, and completes the returned CF * with the number of milliseconds it took to get a valid response. diff --git a/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestExchangeImpl.java b/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestExchangeImpl.java index 28ea572a018..0abcd51af63 100644 --- a/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestExchangeImpl.java +++ b/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestExchangeImpl.java @@ -26,6 +26,7 @@ import jdk.internal.net.http.common.HttpHeadersBuilder; import jdk.internal.net.http.frame.HeaderFrame; import jdk.internal.net.http.frame.HeadersFrame; +import jdk.internal.net.http.frame.Http2Frame; import jdk.internal.net.http.frame.ResetFrame; import javax.net.ssl.SSLSession; @@ -38,6 +39,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.function.BiPredicate; public class Http2TestExchangeImpl implements Http2TestExchange { @@ -131,8 +133,13 @@ public OutputStream getResponseBody() { return os; } - @Override public void sendResponseHeaders(int rCode, long responseLength) throws IOException { + sendResponseHeaders(rCode, responseLength, (n,v) -> false); + } + @Override + public void sendResponseHeaders(int rCode, long responseLength, + BiPredicate insertionPolicy) + throws IOException { // Do not set Content-Length for 100, and do not set END_STREAM if (rCode == 100) responseLength = 0; @@ -146,7 +153,7 @@ public void sendResponseHeaders(int rCode, long responseLength) throws IOExcepti HttpHeaders headers = rspheadersBuilder.build(); Http2TestServerConnection.ResponseHeaders response - = new Http2TestServerConnection.ResponseHeaders(headers); + = new Http2TestServerConnection.ResponseHeaders(headers, insertionPolicy); response.streamid(streamid); response.setFlag(HeaderFrame.END_HEADERS); @@ -167,6 +174,11 @@ public void sendResponseHeaders(int rCode, long responseLength) throws IOExcepti System.err.println("Sent response headers " + rCode); } + @Override + public void sendFrames(List frames) throws IOException { + conn.sendFrames(frames); + } + @Override public InetSocketAddress getRemoteAddress() { return (InetSocketAddress) conn.socket.getRemoteSocketAddress(); diff --git a/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestServerConnection.java b/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestServerConnection.java index e93669685dc..b087a037222 100644 --- a/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestServerConnection.java +++ b/test/jdk/java/net/httpclient/lib/jdk/httpclient/test/lib/http2/Http2TestServerConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,8 @@ package jdk.httpclient.test.lib.http2; import jdk.internal.net.http.common.HttpHeadersBuilder; +import jdk.internal.net.http.common.Log; +import jdk.internal.net.http.frame.ContinuationFrame; import jdk.internal.net.http.frame.DataFrame; import jdk.internal.net.http.frame.ErrorFrame; import jdk.internal.net.http.frame.FramesDecoder; @@ -78,10 +80,13 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.BiPredicate; import java.util.function.Consumer; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; +import static jdk.internal.net.http.frame.SettingsFrame.DEFAULT_MAX_FRAME_SIZE; import static jdk.internal.net.http.frame.SettingsFrame.HEADER_TABLE_SIZE; /** @@ -100,7 +105,7 @@ public class Http2TestServerConnection { final Http2TestExchangeSupplier exchangeSupplier; final InputStream is; final OutputStream os; - volatile Encoder hpackOut; + volatile HpackTestEncoder hpackOut; volatile Decoder hpackIn; volatile SettingsFrame clientSettings; final SettingsFrame serverSettings; @@ -393,7 +398,9 @@ private SettingsFrame getSettingsFromString(String s) throws IOException { } public int getMaxFrameSize() { - return clientSettings.getParameter(SettingsFrame.MAX_FRAME_SIZE); + var max = clientSettings.getParameter(SettingsFrame.MAX_FRAME_SIZE); + if (max <= 0) max = DEFAULT_MAX_FRAME_SIZE; + return max; } /** Sends a pre-canned HTTP/1.1 response. */ @@ -454,7 +461,7 @@ void run() throws Exception { //System.out.println("ServerSettings: " + serverSettings); //System.out.println("ClientSettings: " + clientSettings); - hpackOut = new Encoder(serverSettings.getParameter(HEADER_TABLE_SIZE)); + hpackOut = new HpackTestEncoder(serverSettings.getParameter(HEADER_TABLE_SIZE)); hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE)); if (!secure) { @@ -745,6 +752,14 @@ headers, rspheadersBuilder, uri, bis, getSSLSession(), } } + public void sendFrames(List frames) throws IOException { + synchronized (outputQ) { + for (var frame : frames) { + outputQ.put(frame); + } + } + } + protected HttpHeadersBuilder createNewHeadersBuilder() { return new HttpHeadersBuilder(); } @@ -858,26 +873,38 @@ static boolean isServerStreamId(int streamid) { return (streamid & 0x01) == 0x00; } + final ReentrantLock headersLock = new ReentrantLock(); + /** Encodes an group of headers, without any ordering guarantees. */ public List encodeHeaders(HttpHeaders headers) { + return encodeHeaders(headers, (n,v) -> false); + } + + public List encodeHeaders(HttpHeaders headers, + BiPredicate insertionPolicy) { List buffers = new LinkedList<>(); ByteBuffer buf = getBuffer(); boolean encoded; - for (Map.Entry> entry : headers.map().entrySet()) { - List values = entry.getValue(); - String key = entry.getKey().toLowerCase(); - for (String value : values) { - do { - hpackOut.header(key, value); - encoded = hpackOut.encode(buf); - if (!encoded) { - buf.flip(); - buffers.add(buf); - buf = getBuffer(); - } - } while (!encoded); + headersLock.lock(); + try { + for (Map.Entry> entry : headers.map().entrySet()) { + List values = entry.getValue(); + String key = entry.getKey().toLowerCase(); + for (String value : values) { + hpackOut.header(key, value, insertionPolicy); + do { + encoded = hpackOut.encode(buf); + if (!encoded && !buf.hasRemaining()) { + buf.flip(); + buffers.add(buf); + buf = getBuffer(); + } + } while (!encoded); + } } + } finally { + headersLock.unlock(); } buf.flip(); buffers.add(buf); @@ -890,18 +917,23 @@ public List encodeHeadersOrdered(List> head ByteBuffer buf = getBuffer(); boolean encoded; - for (Map.Entry entry : headers) { - String value = entry.getValue(); - String key = entry.getKey().toLowerCase(); - do { + headersLock.lock(); + try { + for (Map.Entry entry : headers) { + String value = entry.getValue(); + String key = entry.getKey().toLowerCase(); hpackOut.header(key, value); - encoded = hpackOut.encode(buf); - if (!encoded) { - buf.flip(); - buffers.add(buf); - buf = getBuffer(); - } - } while (!encoded); + do { + encoded = hpackOut.encode(buf); + if (!encoded && !buf.hasRemaining()) { + buf.flip(); + buffers.add(buf); + buf = getBuffer(); + } + } while (!encoded); + } + } finally { + headersLock.unlock(); } buf.flip(); buffers.add(buf); @@ -928,10 +960,50 @@ void writeLoop() { break; } else throw x; } - if (frame instanceof ResponseHeaders) { - ResponseHeaders rh = (ResponseHeaders)frame; - HeadersFrame hf = new HeadersFrame(rh.streamid(), rh.getFlags(), encodeHeaders(rh.headers)); - writeFrame(hf); + if (frame instanceof ResponseHeaders rh) { + var buffers = encodeHeaders(rh.headers, rh.insertionPolicy); + int maxFrameSize = Math.min(rh.getMaxFrameSize(), getMaxFrameSize() - 64); + int next = 0; + int cont = 0; + do { + // If the total size of headers exceeds the max frame + // size we need to split the headers into one + // HeadersFrame + N x ContinuationFrames + int remaining = maxFrameSize; + var list = new ArrayList(buffers.size()); + for (; next < buffers.size(); next++) { + var b = buffers.get(next); + var len = b.remaining(); + if (!b.hasRemaining()) continue; + if (len <= remaining) { + remaining -= len; + list.add(b); + } else { + if (next == 0) { + list.add(b.slice(b.position(), remaining)); + b.position(b.position() + remaining); + remaining = 0; + } + break; + } + } + int flags = rh.getFlags(); + if (next != buffers.size()) { + flags = flags & ~HeadersFrame.END_HEADERS; + } + if (cont > 0) { + flags = flags & ~HeadersFrame.END_STREAM; + } + HeaderFrame hf = cont == 0 + ? new HeadersFrame(rh.streamid(), flags, list) + : new ContinuationFrame(rh.streamid(), flags, list); + if (Log.headers()) { + // avoid too much chatter: log only if Log.headers() is enabled + System.err.println("TestServer writing " + hf); + } + writeFrame(hf); + cont++; + } while (next < buffers.size()); } else if (frame instanceof OutgoingPushPromise) { handlePush((OutgoingPushPromise)frame); } else @@ -1242,11 +1314,29 @@ synchronized void updateConnectionWindow(int amount) { // for the hashmap. static class ResponseHeaders extends Http2Frame { - HttpHeaders headers; + final HttpHeaders headers; + final BiPredicate insertionPolicy; - ResponseHeaders(HttpHeaders headers) { + final int maxFrameSize; + + public ResponseHeaders(HttpHeaders headers) { + this(headers, (n,v) -> false); + } + public ResponseHeaders(HttpHeaders headers, BiPredicate insertionPolicy) { + this(headers, insertionPolicy, Integer.MAX_VALUE); + } + + public ResponseHeaders(HttpHeaders headers, + BiPredicate insertionPolicy, + int maxFrameSize) { super(0, 0); this.headers = headers; + this.insertionPolicy = insertionPolicy; + this.maxFrameSize = maxFrameSize; + } + + public int getMaxFrameSize() { + return maxFrameSize; } } From 5304d9bfeec85504d5ba6d5099f2989888ef252a Mon Sep 17 00:00:00 2001 From: Martin Balao Date: Wed, 14 Aug 2024 21:12:00 +0000 Subject: [PATCH 3/7] 8328544: Improve handling of vectorization Reviewed-by: roland, yan Backport-of: b5174c9159fbffdf335ee6835267ba0e674cf432 --- .../TestVectorizationMismatchedAccess.java | 334 +++++++++++++++--- ...tIndependentPacksWithCyclicDependency.java | 140 ++++---- ...IndependentPacksWithCyclicDependency2.java | 28 +- .../TestScheduleReordersScalarMemops.java | 8 +- 4 files changed, 367 insertions(+), 143 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java index 4117bc98c3c..c2bd9da20f5 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2023, Red Hat, Inc. All rights reserved. + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,7 +36,6 @@ * @test * @bug 8300258 * @key randomness - * @requires (os.simpleArch == "x64") | (os.simpleArch == "aarch64") * @summary C2: vectorization fails on simple ByteBuffer loop * @modules java.base/jdk.internal.misc * @library /test/lib / @@ -150,190 +150,414 @@ static private void runAndVerify3(Runnable test, int offset) { } @Test - @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testByteLong1(byte[] dest, long[] src) { + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned). + // might get fixed with JDK-8325155. + public static void testByteLong1a(byte[] dest, long[] src) { for (int i = 0; i < src.length; i++) { UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i, src[i]); } } - @Run(test = "testByteLong1") + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: address has ConvL2I for cast of long to address, not supported. + public static void testByteLong1b(byte[] dest, long[] src) { + for (int i = 0; i < src.length; i++) { + UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * i, src[i]); + } + } + + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}) + public static void testByteLong1c(byte[] dest, long[] src) { + long base = 64; // make sure it is big enough and 8 byte aligned (required for 32-bit) + for (int i = 0; i < src.length - 8; i++) { + UNSAFE.putLongUnaligned(dest, base + 8 * i, src[i]); + } + } + + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: address has ConvL2I for cast of long to address, not supported. + public static void testByteLong1d(byte[] dest, long[] src) { + long base = 64; // make sure it is big enough and 8 byte aligned (required for 32-bit) + for (int i = 0; i < src.length - 8; i++) { + UNSAFE.putLongUnaligned(dest, base + 8L * i, src[i]); + } + } + + @Run(test = {"testByteLong1a", "testByteLong1b", "testByteLong1c", "testByteLong1d"}) public static void testByteLong1_runner() { - runAndVerify(() -> testByteLong1(byteArray, longArray), 0); + runAndVerify(() -> testByteLong1a(byteArray, longArray), 0); + runAndVerify(() -> testByteLong1b(byteArray, longArray), 0); + testByteLong1c(byteArray, longArray); + testByteLong1d(byteArray, longArray); } @Test - @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testByteLong2(byte[] dest, long[] src) { + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned). + // might get fixed with JDK-8325155. + public static void testByteLong2a(byte[] dest, long[] src) { for (int i = 1; i < src.length; i++) { UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * (i - 1), src[i]); } } - @Run(test = "testByteLong2") + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: address has ConvL2I for cast of long to address, not supported. + public static void testByteLong2b(byte[] dest, long[] src) { + for (int i = 1; i < src.length; i++) { + UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * (i - 1), src[i]); + } + } + + @Run(test = {"testByteLong2a", "testByteLong2b"}) public static void testByteLong2_runner() { - runAndVerify(() -> testByteLong2(byteArray, longArray), -8); + runAndVerify(() -> testByteLong2a(byteArray, longArray), -8); + runAndVerify(() -> testByteLong2b(byteArray, longArray), -8); } @Test - @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testByteLong3(byte[] dest, long[] src) { + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned). + // might get fixed with JDK-8325155. + public static void testByteLong3a(byte[] dest, long[] src) { for (int i = 0; i < src.length - 1; i++) { UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * (i + 1), src[i]); } } - @Run(test = "testByteLong3") + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: address has ConvL2I for cast of long to address, not supported. + public static void testByteLong3b(byte[] dest, long[] src) { + for (int i = 0; i < src.length - 1; i++) { + UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * (i + 1), src[i]); + } + } + + @Run(test = {"testByteLong3a", "testByteLong3b"}) public static void testByteLong3_runner() { - runAndVerify(() -> testByteLong3(byteArray, longArray), 8); + runAndVerify(() -> testByteLong3a(byteArray, longArray), 8); + runAndVerify(() -> testByteLong3b(byteArray, longArray), 8); } @Test - @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testByteLong4(byte[] dest, long[] src, int start, int stop) { + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned). + // might get fixed with JDK-8325155. + public static void testByteLong4a(byte[] dest, long[] src, int start, int stop) { for (int i = start; i < stop; i++) { UNSAFE.putLongUnaligned(dest, 8 * i + baseOffset, src[i]); } } - @Run(test = "testByteLong4") + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: address has ConvL2I for cast of long to address, not supported. + public static void testByteLong4b(byte[] dest, long[] src, int start, int stop) { + for (int i = start; i < stop; i++) { + UNSAFE.putLongUnaligned(dest, 8L * i + baseOffset, src[i]); + } + } + + @Run(test = {"testByteLong4a", "testByteLong4b"}) public static void testByteLong4_runner() { baseOffset = UNSAFE.ARRAY_BYTE_BASE_OFFSET; - runAndVerify(() -> testByteLong4(byteArray, longArray, 0, size), 0); + runAndVerify(() -> testByteLong4a(byteArray, longArray, 0, size), 0); + runAndVerify(() -> testByteLong4b(byteArray, longArray, 0, size), 0); } @Test - @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testByteLong5(byte[] dest, long[] src, int start, int stop) { + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned). + // might get fixed with JDK-8325155. + public static void testByteLong5a(byte[] dest, long[] src, int start, int stop) { for (int i = start; i < stop; i++) { UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * (i + baseOffset), src[i]); } } - @Run(test = "testByteLong5") + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: address has ConvL2I for cast of long to address, not supported. + public static void testByteLong5b(byte[] dest, long[] src, int start, int stop) { + for (int i = start; i < stop; i++) { + UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * (i + baseOffset), src[i]); + } + } + + @Run(test = {"testByteLong5a", "testByteLong5b"}) public static void testByteLong5_runner() { baseOffset = 1; - runAndVerify(() -> testByteLong5(byteArray, longArray, 0, size-1), 8); + runAndVerify(() -> testByteLong5a(byteArray, longArray, 0, size-1), 8); + runAndVerify(() -> testByteLong5b(byteArray, longArray, 0, size-1), 8); } @Test - @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testByteByte1(byte[] dest, byte[] src) { + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned). + // might get fixed with JDK-8325155. + public static void testByteByte1a(byte[] dest, byte[] src) { for (int i = 0; i < src.length / 8; i++) { UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i, UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i)); } } - @Run(test = "testByteByte1") + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + applyIfPlatform = {"64-bit", "true"}) + // 32-bit: address has ConvL2I for cast of long to address, not supported. + public static void testByteByte1b(byte[] dest, byte[] src) { + for (int i = 0; i < src.length / 8; i++) { + UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * i, UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * i)); + } + } + + @Run(test = {"testByteByte1a", "testByteByte1b"}) public static void testByteByte1_runner() { - runAndVerify2(() -> testByteByte1(byteArray, byteArray), 0); + runAndVerify2(() -> testByteByte1a(byteArray, byteArray), 0); + runAndVerify2(() -> testByteByte1b(byteArray, byteArray), 0); } - // It would be legal to vectorize this one but it's not currently @Test - //@IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testByteByte2(byte[] dest, byte[] src) { + // It would be legal to vectorize this one but it's not currently + //@IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + // applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + // applyIfPlatform = {"64-bit", "true"}) + // 32-bit: offsets are badly aligned (UNSAFE.ARRAY_BYTE_BASE_OFFSET is 4 byte aligned, but not 8 byte aligned). + // might get fixed with JDK-8325155. + public static void testByteByte2a(byte[] dest, byte[] src) { for (int i = 1; i < src.length / 8; i++) { UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * (i - 1), UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i)); } } - @Run(test = "testByteByte2") + @Test + // It would be legal to vectorize this one but it's not currently + //@IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }, + // applyIfCPUFeatureOr = {"sse2", "true", "asimd", "true"}, + // applyIfPlatform = {"64-bit", "true"}) + // 32-bit: address has ConvL2I for cast of long to address, not supported. + public static void testByteByte2b(byte[] dest, byte[] src) { + for (int i = 1; i < src.length / 8; i++) { + UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * (i - 1), UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * i)); + } + } + + @Run(test = {"testByteByte2a", "testByteByte2b"}) public static void testByteByte2_runner() { - runAndVerify2(() -> testByteByte2(byteArray, byteArray), -8); + runAndVerify2(() -> testByteByte2a(byteArray, byteArray), -8); + runAndVerify2(() -> testByteByte2b(byteArray, byteArray), -8); } @Test @IR(failOn = { IRNode.LOAD_VECTOR_L, IRNode.STORE_VECTOR }) - public static void testByteByte3(byte[] dest, byte[] src) { + public static void testByteByte3a(byte[] dest, byte[] src) { for (int i = 0; i < src.length / 8 - 1; i++) { UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * (i + 1), UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i)); } } - @Run(test = "testByteByte3") + @Test + @IR(failOn = { IRNode.LOAD_VECTOR_L, IRNode.STORE_VECTOR }) + public static void testByteByte3b(byte[] dest, byte[] src) { + for (int i = 0; i < src.length / 8 - 1; i++) { + UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * (i + 1), UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * i)); + } + } + + @Run(test = {"testByteByte3a", "testByteByte3b"}) public static void testByteByte3_runner() { - runAndVerify2(() -> testByteByte3(byteArray, byteArray), 8); + runAndVerify2(() -> testByteByte3a(byteArray, byteArray), 8); + runAndVerify2(() -> testByteByte3b(byteArray, byteArray), 8); } @Test @IR(failOn = { IRNode.LOAD_VECTOR_L, IRNode.STORE_VECTOR }) - public static void testByteByte4(byte[] dest, byte[] src, int start, int stop) { + public static void testByteByte4a(byte[] dest, byte[] src, int start, int stop) { for (int i = start; i < stop; i++) { UNSAFE.putLongUnaligned(dest, 8 * i + baseOffset, UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i)); } } - @Run(test = "testByteByte4") + @Test + @IR(failOn = { IRNode.LOAD_VECTOR_L, IRNode.STORE_VECTOR }) + public static void testByteByte4b(byte[] dest, byte[] src, int start, int stop) { + for (int i = start; i < stop; i++) { + UNSAFE.putLongUnaligned(dest, 8L * i + baseOffset, UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * i)); + } + } + + @Run(test = {"testByteByte4a", "testByteByte4b"}) public static void testByteByte4_runner() { baseOffset = UNSAFE.ARRAY_BYTE_BASE_OFFSET; - runAndVerify2(() -> testByteByte4(byteArray, byteArray, 0, size), 0); + runAndVerify2(() -> testByteByte4a(byteArray, byteArray, 0, size), 0); + runAndVerify2(() -> testByteByte4b(byteArray, byteArray, 0, size), 0); } @Test @IR(failOn = { IRNode.LOAD_VECTOR_L, IRNode.STORE_VECTOR }) - public static void testByteByte5(byte[] dest, byte[] src, int start, int stop) { + public static void testByteByte5a(byte[] dest, byte[] src, int start, int stop) { for (int i = start; i < stop; i++) { UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * (i + baseOffset), UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8 * i)); } } - @Run(test = "testByteByte5") + @Test + @IR(failOn = { IRNode.LOAD_VECTOR_L, IRNode.STORE_VECTOR }) + public static void testByteByte5b(byte[] dest, byte[] src, int start, int stop) { + for (int i = start; i < stop; i++) { + UNSAFE.putLongUnaligned(dest, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * (i + baseOffset), UNSAFE.getLongUnaligned(src, UNSAFE.ARRAY_BYTE_BASE_OFFSET + 8L * i)); + } + } + + @Run(test = {"testByteByte5a", "testByteByte5b"}) public static void testByteByte5_runner() { baseOffset = 1; - runAndVerify2(() -> testByteByte5(byteArray, byteArray, 0, size-1), 8); + runAndVerify2(() -> testByteByte5a(byteArray, byteArray, 0, size-1), 8); + runAndVerify2(() -> testByteByte5b(byteArray, byteArray, 0, size-1), 8); } @Test - @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testOffHeapLong1(long dest, long[] src) { + @IR(counts = { IRNode.LOAD_VECTOR_L, "=0", IRNode.STORE_VECTOR, "=0" }) // temporary + // @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) + // FAILS: adr is CastX2P(dest + 8 * (i + int_con)) + // See: JDK-8331576 + public static void testOffHeapLong1a(long dest, long[] src) { for (int i = 0; i < src.length; i++) { UNSAFE.putLongUnaligned(null, dest + 8 * i, src[i]); } } - @Run(test = "testOffHeapLong1") + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, "=0", IRNode.STORE_VECTOR, "=0" }) // temporary + // @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) + // FAILS: adr is CastX2P(dest + 8L * (i + int_con)) + // See: JDK-8331576 + public static void testOffHeapLong1b(long dest, long[] src) { + for (int i = 0; i < src.length; i++) { + UNSAFE.putLongUnaligned(null, dest + 8L * i, src[i]); + } + } + + @Run(test = {"testOffHeapLong1a", "testOffHeapLong1b"}) public static void testOffHeapLong1_runner() { - runAndVerify3(() -> testOffHeapLong1(baseOffHeap, longArray), 0); + runAndVerify3(() -> testOffHeapLong1a(baseOffHeap, longArray), 0); + runAndVerify3(() -> testOffHeapLong1b(baseOffHeap, longArray), 0); } @Test - @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testOffHeapLong2(long dest, long[] src) { + @IR(counts = { IRNode.LOAD_VECTOR_L, "=0", IRNode.STORE_VECTOR, "=0" }) // temporary + // @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) + // FAILS: adr is CastX2P + // See: JDK-8331576 + public static void testOffHeapLong2a(long dest, long[] src) { for (int i = 1; i < src.length; i++) { UNSAFE.putLongUnaligned(null, dest + 8 * (i - 1), src[i]); } } - @Run(test = "testOffHeapLong2") + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, "=0", IRNode.STORE_VECTOR, "=0" }) // temporary + // @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) + // FAILS: adr is CastX2P + // See: JDK-8331576 + public static void testOffHeapLong2b(long dest, long[] src) { + for (int i = 1; i < src.length; i++) { + UNSAFE.putLongUnaligned(null, dest + 8L * (i - 1), src[i]); + } + } + + @Run(test = {"testOffHeapLong2a", "testOffHeapLong2b"}) public static void testOffHeapLong2_runner() { - runAndVerify3(() -> testOffHeapLong2(baseOffHeap, longArray), -8); + runAndVerify3(() -> testOffHeapLong2a(baseOffHeap, longArray), -8); + runAndVerify3(() -> testOffHeapLong2b(baseOffHeap, longArray), -8); } @Test - @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testOffHeapLong3(long dest, long[] src) { + @IR(counts = { IRNode.LOAD_VECTOR_L, "=0", IRNode.STORE_VECTOR, "=0" }) // temporary + // @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) + // FAILS: adr is CastX2P + // See: JDK-8331576 + public static void testOffHeapLong3a(long dest, long[] src) { for (int i = 0; i < src.length - 1; i++) { UNSAFE.putLongUnaligned(null, dest + 8 * (i + 1), src[i]); } } - @Run(test = "testOffHeapLong3") + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, "=0", IRNode.STORE_VECTOR, "=0" }) // temporary + // @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) + // FAILS: adr is CastX2P + // See: JDK-8331576 + public static void testOffHeapLong3b(long dest, long[] src) { + for (int i = 0; i < src.length - 1; i++) { + UNSAFE.putLongUnaligned(null, dest + 8L * (i + 1), src[i]); + } + } + + @Run(test = {"testOffHeapLong3a", "testOffHeapLong3b"}) public static void testOffHeapLong3_runner() { - runAndVerify3(() -> testOffHeapLong3(baseOffHeap, longArray), 8); + runAndVerify3(() -> testOffHeapLong3a(baseOffHeap, longArray), 8); + runAndVerify3(() -> testOffHeapLong3b(baseOffHeap, longArray), 8); } @Test - @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) - public static void testOffHeapLong4(long dest, long[] src, int start, int stop) { + @IR(counts = { IRNode.LOAD_VECTOR_L, "=0", IRNode.STORE_VECTOR, "=0" }) // temporary + // @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) + // FAILS: adr is CastX2P + // See: JDK-8331576 + public static void testOffHeapLong4a(long dest, long[] src, int start, int stop) { for (int i = start; i < stop; i++) { UNSAFE.putLongUnaligned(null, dest + 8 * i + baseOffset, src[i]); } } - @Run(test = "testOffHeapLong4") + @Test + @IR(counts = { IRNode.LOAD_VECTOR_L, "=0", IRNode.STORE_VECTOR, "=0" }) // temporary + // @IR(counts = { IRNode.LOAD_VECTOR_L, ">=1", IRNode.STORE_VECTOR, ">=1" }) + // FAILS: adr is CastX2P + // See: JDK-8331576 + public static void testOffHeapLong4b(long dest, long[] src, int start, int stop) { + for (int i = start; i < stop; i++) { + UNSAFE.putLongUnaligned(null, dest + 8L * i + baseOffset, src[i]); + } + } + + @Run(test = {"testOffHeapLong4a", "testOffHeapLong4b"}) public static void testOffHeapLong4_runner() { baseOffset = 8; - runAndVerify3(() -> testOffHeapLong4(baseOffHeap, longArray, 0, size-1), 8); + runAndVerify3(() -> testOffHeapLong4a(baseOffHeap, longArray, 0, size-1), 8); + runAndVerify3(() -> testOffHeapLong4b(baseOffHeap, longArray, 0, size-1), 8); } } diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency.java index b2748348036..b594b446234 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency.java @@ -171,10 +171,10 @@ public void runTest2() { static void test2(int[] dataIa, int[] dataIb, float[] dataFa, float[] dataFb) { for (int i = 0; i < RANGE; i+=2) { // int and float arrays are two slices. But we pretend both are of type int. - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0, dataIa[i+0] + 1); - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4, dataIa[i+1] + 1); - dataIb[i+0] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0); - dataIb[i+1] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4); + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0, dataIa[i+0] + 1); + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4, dataIa[i+1] + 1); + dataIb[i+0] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0); + dataIb[i+1] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4); } } @@ -246,10 +246,10 @@ static void test5(int[] dataIa, int[] dataIb, float[] dataFa, float[] dataFb) { for (int i = 0; i < RANGE; i+=2) { // same as test2, except that reordering leads to different semantics // explanation analogue to test4 - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0, dataIa[i+0] + 1); // A - dataIb[i+0] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0); // X - dataIb[i+1] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4); // Y - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4, dataIa[i+1] + 1); // B + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0, dataIa[i+0] + 1); // A + dataIb[i+0] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0); // X + dataIb[i+1] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4); // Y + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4, dataIa[i+1] + 1); // B } } @@ -272,18 +272,18 @@ static void test6(int[] dataIa, int[] dataIb, float[] dataFa, float[] dataFb, long[] dataLa, long[] dataLb) { for (int i = 0; i < RANGE; i+=2) { // Chain of parallelizable op and conversion - int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0) + 3; - int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4) + 3; - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0, v00); - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4, v01); - int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0) * 45; - int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4) * 45; - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0, v10); - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4, v11); - float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0) + 0.55f; - float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4) + 0.55f; - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0, v20); - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4, v21); + int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0) + 3; + int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4) + 3; + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0, v00); + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4, v01); + int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0) * 45; + int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4) * 45; + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0, v10); + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4, v11); + float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0) + 0.55f; + float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4) + 0.55f; + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0, v20); + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4, v21); } } @@ -304,18 +304,18 @@ static void test7(int[] dataIa, int[] dataIb, float[] dataFa, float[] dataFb, long[] dataLa, long[] dataLb) { for (int i = 0; i < RANGE; i+=2) { // Cycle involving 3 memory slices - int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0) + 3; - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0, v00); - int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0) * 45; - int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4) * 45; - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0, v10); - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4, v11); - float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0) + 0.55f; - float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4) + 0.55f; - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0, v20); - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4, v21); - int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4) + 3; // moved down - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4, v01); + int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0) + 3; + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0, v00); + int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0) * 45; + int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4) * 45; + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0, v10); + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4, v11); + float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0) + 0.55f; + float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4) + 0.55f; + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0, v20); + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4, v21); + int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4) + 3; // moved down + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4, v01); } } @@ -337,19 +337,19 @@ static void test8(int[] dataIa, int[] dataIb, float[] dataFa, float[] dataFb, long[] dataLa, long[] dataLb) { for (int i = 0; i < RANGE; i+=2) { // 2-cycle, with more ops after - int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0) + 3; - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0, v00); - int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0) * 45; - int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4) * 45; - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0, v10); - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4, v11); - int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4) + 3; - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4, v01); + int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0) + 3; + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0, v00); + int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0) * 45; + int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4) * 45; + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0, v10); + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4, v11); + int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4) + 3; + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4, v01); // more stuff after - float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0) + 0.55f; - float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4) + 0.55f; - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0, v20); - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4, v21); + float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0) + 0.55f; + float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4) + 0.55f; + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0, v20); + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4, v21); } } @@ -370,19 +370,19 @@ static void test9(int[] dataIa, int[] dataIb, float[] dataFa, float[] dataFb, long[] dataLa, long[] dataLb) { for (int i = 0; i < RANGE; i+=2) { // 2-cycle, with more stuff before - float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0) + 0.55f; - float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4) + 0.55f; - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0, v20); - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4, v21); + float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0) + 0.55f; + float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4) + 0.55f; + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0, v20); + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4, v21); // 2-cycle - int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0) + 3; - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0, v00); - int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0) * 45; - int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4) * 45; - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0, v10); - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4, v11); - int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4) + 3; - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4, v01); + int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0) + 3; + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0, v00); + int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0) * 45; + int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4) * 45; + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0, v10); + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4, v11); + int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4) + 3; + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4, v01); } } @@ -420,18 +420,18 @@ static void test10(int[] dataIa, int[] dataIb, float[] dataFa, float[] dataFb, // // The cycle thus does not only go via packs, but also scalar ops. // - int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0) + 3; // A - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0, v00); - int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0) * 45; // R: constant mismatch - int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4) + 43; // S - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0, v10); - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4, v11); - float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0) + 0.55f; // U - float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4) + 0.55f; // V - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0, v20); - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4, v21); - int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4) + 3; // B: moved down - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4, v01); + int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0) + 3; // A + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0, v00); + int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0) * 45; // R: constant mismatch + int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4) + 43; // S + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0, v10); + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4, v11); + float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0) + 0.55f; // U + float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4) + 0.55f; // V + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0, v20); + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4, v21); + int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4) + 3; // B: moved down + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4, v01); } } @@ -460,8 +460,8 @@ static void verify(String name, int[] data, int[] gold) { static void verify(String name, float[] data, float[] gold) { for (int i = 0; i < RANGE; i++) { - int datav = unsafe.getInt(data, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i); - int goldv = unsafe.getInt(gold, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i); + int datav = unsafe.getInt(data, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i); + int goldv = unsafe.getInt(gold, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i); if (datav != goldv) { throw new RuntimeException(" Invalid " + name + " result: dataF[" + i + "]: " + datav + " != " + goldv); } diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency2.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency2.java index 31a2ba3a0cb..867363fb520 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency2.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestIndependentPacksWithCyclicDependency2.java @@ -59,18 +59,18 @@ static void test(int[] dataIa, int[] dataIb, float[] dataFa, float[] dataFb, long[] dataLa, long[] dataLb) { for (int i = 0; i < RANGE; i+=2) { // For explanation, see test 10 in TestIndependentPacksWithCyclicDependency.java - int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0) + 3; - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0, v00); - int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0) * 45; - int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4) + 43; - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0, v10); - unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4, v11); - float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 0) + 0.55f; - float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4 * i + 4) + 0.55f; - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0, v20); - unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4, v21); - int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4) + 3; // moved down - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4, v01); + int v00 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0) + 3; + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0, v00); + int v10 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0) * 45; + int v11 = unsafe.getInt(dataFb, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4) + 43; + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0, v10); + unsafe.putInt(dataLa, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4, v11); + float v20 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 0) + 0.55f; + float v21 = unsafe.getFloat(dataLb, unsafe.ARRAY_LONG_BASE_OFFSET + 4L * i + 4) + 0.55f; + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0, v20); + unsafe.putFloat(dataIb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4, v21); + int v01 = unsafe.getInt(dataIa, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4) + 3; // moved down + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4, v01); } } @@ -84,8 +84,8 @@ static void verify(String name, int[] data, int[] gold) { static void verify(String name, float[] data, float[] gold) { for (int i = 0; i < RANGE; i++) { - int datav = unsafe.getInt(data, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i); - int goldv = unsafe.getInt(gold, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i); + int datav = unsafe.getInt(data, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i); + int goldv = unsafe.getInt(gold, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i); if (datav != goldv) { throw new RuntimeException(" Invalid " + name + " result: dataF[" + i + "]: " + datav + " != " + goldv); } diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestScheduleReordersScalarMemops.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestScheduleReordersScalarMemops.java index f74c19d1b8b..416c360d6f1 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestScheduleReordersScalarMemops.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestScheduleReordersScalarMemops.java @@ -125,10 +125,10 @@ static void test1(int[] dataIa, int[] dataIb, float[] dataFa, float[] dataFb) { for (int i = 0; i < RANGE; i+=2) { // Do the same as test0, but without int-float conversion. // This should reproduce on machines where conversion is not implemented. - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 0, dataIa[i+0] + 1); // A +1 - dataIb[i+0] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 0); // X - dataIb[i+1] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4 * i + 4); // Y - unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4 * i + 4, dataIa[i+1] * 11); // B *11 + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 0, dataIa[i+0] + 1); // A +1 + dataIb[i+0] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 0); // X + dataIb[i+1] = 11 * unsafe.getInt(dataFb, unsafe.ARRAY_INT_BASE_OFFSET + 4L * i + 4); // Y + unsafe.putInt(dataFa, unsafe.ARRAY_FLOAT_BASE_OFFSET + 4L * i + 4, dataIa[i+1] * 11); // B *11 } } From 4849f000a3c6d535b709aed8774efa8c4c4f3c63 Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Fri, 10 May 2024 12:04:46 -0700 Subject: [PATCH 4/7] 8328726: Better Kerberos support Reviewed-by: mbalao Backport-of: 7325899a11f17bf4516d39495a12796385e459ed --- .../security/auth/kerberos/EncryptionKey.java | 4 ++-- .../auth/kerberos/KerberosCredMessage.java | 6 ++--- .../security/auth/kerberos/KerberosKey.java | 8 +++---- .../javax/security/auth/kerberos/KeyImpl.java | 16 +++++--------- .../sun/security/jgss/krb5/Krb5Context.java | 22 +++++-------------- .../sun/security/jgss/krb5/Krb5Util.java | 15 +++++++++++++ .../sun/security/krb5/EncryptionKey.java | 8 ++----- .../sun/security/krb5/internal/Krb5.java | 3 --- .../security/krb5/internal/tools/Kinit.java | 4 ---- .../pkcs11/wrapper/CK_PBE_PARAMS.java | 5 ----- .../security/auth/module/Krb5LoginModule.java | 10 +++------ 11 files changed, 39 insertions(+), 62 deletions(-) diff --git a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/EncryptionKey.java b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/EncryptionKey.java index 08463c883ac..d3a62f98eef 100644 --- a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/EncryptionKey.java +++ b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/EncryptionKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -170,7 +170,7 @@ public String toString() { if (destroyed) { return "Destroyed EncryptionKey"; } - return "key " + key.toString(); + return "EncryptionKey: " + key.toString(); } /** diff --git a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosCredMessage.java b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosCredMessage.java index 3045fd78180..52ae9d64647 100644 --- a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosCredMessage.java +++ b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosCredMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,6 @@ import javax.security.auth.Destroyable; import java.util.Arrays; -import java.util.Base64; import java.util.Objects; /** @@ -140,8 +139,7 @@ public String toString() { if (destroyed) { return "Destroyed KerberosCredMessage"; } else { - return "KRB_CRED from " + sender + " to " + recipient + ":\n" - + Base64.getUrlEncoder().encodeToString(message); + return "KRB_CRED from " + sender + " to " + recipient; } } diff --git a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosKey.java b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosKey.java index 5c11c23d63a..583c02ba170 100644 --- a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosKey.java +++ b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KerberosKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -272,9 +272,9 @@ public String toString() { if (destroyed) { return "Destroyed KerberosKey"; } - return "Kerberos Principal " + principal + - "Key Version " + versionNum + - "key " + key.toString(); + return "KerberosKey: principal " + principal + + ", version " + versionNum + + ", key " + key.toString(); } /** diff --git a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KeyImpl.java b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KeyImpl.java index 46168cf8377..b18f7d8eae1 100644 --- a/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KeyImpl.java +++ b/src/java.security.jgss/share/classes/javax/security/auth/kerberos/KeyImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +30,8 @@ import javax.crypto.SecretKey; import javax.security.auth.Destroyable; import javax.security.auth.DestroyFailedException; -import sun.security.util.HexDumpEncoder; + +import sun.security.jgss.krb5.Krb5Util; import sun.security.krb5.Asn1Exception; import sun.security.krb5.PrincipalName; import sun.security.krb5.EncryptionKey; @@ -225,15 +226,8 @@ private void readObject(ObjectInputStream ois) } public String toString() { - HexDumpEncoder hd = new HexDumpEncoder(); - return "EncryptionKey: keyType=" + keyType - + " keyBytes (hex dump)=" - + (keyBytes == null || keyBytes.length == 0 ? - " Empty Key" : - '\n' + hd.encodeBuffer(keyBytes) - + '\n'); - - + return "keyType=" + keyType + + ", " + Krb5Util.keyInfo(keyBytes); } public int hashCode() { diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java index c0d532a040a..0a4b972bfc7 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Context.java @@ -898,15 +898,11 @@ public final int getWrapSizeLimit(int qop, boolean confReq, public final byte[] wrap(byte[] inBuf, int offset, int len, MessageProp msgProp) throws GSSException { - if (DEBUG) { - System.out.println("Krb5Context.wrap: data=[" - + getHexBytes(inBuf, offset, len) - + "]"); - } - if (state != STATE_DONE) - throw new GSSException(GSSException.NO_CONTEXT, -1, - "Wrap called in invalid state!"); + if (state != STATE_DONE) { + throw new GSSException(GSSException.NO_CONTEXT, -1, + "Wrap called in invalid state!"); + } byte[] encToken = null; try { @@ -1049,12 +1045,6 @@ public final byte[] unwrap(byte[] inBuf, int offset, int len, setSequencingAndReplayProps(token, msgProp); } - if (DEBUG) { - System.out.println("Krb5Context.unwrap: data=[" - + getHexBytes(data, 0, data.length) - + "]"); - } - return data; } @@ -1404,8 +1394,8 @@ public byte[] getEncoded() { @Override public String toString() { - return "Kerberos session key: etype: " + key.getEType() + "\n" + - new HexDumpEncoder().encodeBuffer(key.getBytes()); + return "Kerberos session key: etype=" + key.getEType() + + ", " + Krb5Util.keyInfo(key.getBytes()); } } diff --git a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Util.java b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Util.java index 70b72f18784..94f8510c255 100644 --- a/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Util.java +++ b/src/java.security.jgss/share/classes/sun/security/jgss/krb5/Krb5Util.java @@ -191,4 +191,19 @@ public static EncryptionKey[] keysFromJavaxKeyTab( KeyTab ktab, PrincipalName cname) { return snapshotFromJavaxKeyTab(ktab).readServiceKeys(cname); } + + public static String keyInfo(byte[] data) { + if (data == null) { + return "null key"; + } else if (data.length == 0) { + return "empty key"; + } else { + for (byte b : data) { + if (b != 0) { + return data.length + "-byte key"; + } + } + return data.length + "-byte zero key"; + } + } } diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/EncryptionKey.java b/src/java.security.jgss/share/classes/sun/security/krb5/EncryptionKey.java index e250e98eb42..ef2df3abfdc 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/EncryptionKey.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/EncryptionKey.java @@ -31,6 +31,7 @@ package sun.security.krb5; +import sun.security.jgss.krb5.Krb5Util; import sun.security.util.*; import sun.security.krb5.internal.*; import sun.security.krb5.internal.crypto.*; @@ -498,12 +499,7 @@ public synchronized void writeKey(CCacheOutputStream cos) public String toString() { return "EncryptionKey: keyType=" + keyType - + " kvno=" + kvno - + " keyValue (hex dump)=" - + (keyValue == null || keyValue.length == 0 ? - " Empty Key" : '\n' - + Krb5.hexDumper.encodeBuffer(keyValue) - + '\n'); + + ", kvno=" + kvno + ", " + Krb5Util.keyInfo(keyValue); } /** diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/internal/Krb5.java b/src/java.security.jgss/share/classes/sun/security/krb5/internal/Krb5.java index fabff57ae64..93831f5a3e6 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/internal/Krb5.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/internal/Krb5.java @@ -315,9 +315,6 @@ public static String getErrorMessage(int i) { public static final boolean DEBUG = GetBooleanAction .privilegedGetProperty("sun.security.krb5.debug"); - public static final sun.security.util.HexDumpEncoder hexDumper = - new sun.security.util.HexDumpEncoder(); - static { errMsgList = new Hashtable (); errMsgList.put(KDC_ERR_NONE, "No error"); diff --git a/src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/Kinit.java b/src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/Kinit.java index 813939643c3..4f124771d51 100644 --- a/src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/Kinit.java +++ b/src/java.security.jgss/windows/classes/sun/security/krb5/internal/tools/Kinit.java @@ -192,10 +192,6 @@ private void acquire() System.out.print("Password for " + princName + ":"); System.out.flush(); psswd = Password.readPassword(System.in); - if (DEBUG) { - System.out.println(">>> Kinit console input " + - new String(psswd)); - } } builder = new KrbAsReqBuilder(principal, psswd); } else { diff --git a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_PBE_PARAMS.java b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_PBE_PARAMS.java index a25fa1c39e5..d6c291ebc57 100644 --- a/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_PBE_PARAMS.java +++ b/src/jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/wrapper/CK_PBE_PARAMS.java @@ -127,11 +127,6 @@ public String toString() { sb.append(pPassword.length); sb.append(Constants.NEWLINE); - sb.append(Constants.INDENT); - sb.append("pPassword: "); - sb.append(pPassword); - sb.append(Constants.NEWLINE); - sb.append(Constants.INDENT); sb.append("ulSaltLen: "); sb.append(pSalt.length); diff --git a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/Krb5LoginModule.java b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/Krb5LoginModule.java index 8147fa76a37..c295a2ba628 100644 --- a/src/jdk.security.auth/share/classes/com/sun/security/auth/module/Krb5LoginModule.java +++ b/src/jdk.security.auth/share/classes/com/sun/security/auth/module/Krb5LoginModule.java @@ -42,7 +42,7 @@ import sun.security.krb5.*; import sun.security.jgss.krb5.Krb5Util; import sun.security.krb5.Credentials; -import sun.security.util.HexDumpEncoder; + import static sun.security.util.ResourcesMgr.getAuthResourceString; /** @@ -765,15 +765,11 @@ private void attemptAuthentication(boolean getPasswdFromSharedState) if (debug) { System.out.println("principal is " + principal); - HexDumpEncoder hd = new HexDumpEncoder(); if (ktab != null) { System.out.println("Will use keytab"); } else if (storeKey) { for (int i = 0; i < encKeys.length; i++) { - System.out.println("EncryptionKey: keyType=" + - encKeys[i].getEType() + - " keyBytes (hex dump)=" + - hd.encodeBuffer(encKeys[i].getBytes())); + System.out.println(encKeys[i].toString()); } } } @@ -870,7 +866,7 @@ private void promptForPass(boolean getPasswdFromSharedState) } if (debug) { System.out.println - ("password is " + new String(password)); + ("Get password from shared state"); } return; } From 918dd5973f285304eb6bbfa7ff0a84cc8b0aa606 Mon Sep 17 00:00:00 2001 From: Alexei Voitylov Date: Tue, 13 Aug 2024 10:30:49 +0200 Subject: [PATCH 5/7] 8331446: Improve deserialization support Reviewed-by: yan, mbalao, andrew Backport-of: 8e4a392832f83e16d521024505b52c96d0a993f2 --- .../classes/java/text/MessageFormat.java | 60 ++++++++++-- .../MessageFormat/MaxArgumentIndexTest.java | 97 +++++++++++++++++++ .../MessageFormat/SerializationTest.java | 96 ++++++++++++++++++ 3 files changed, 243 insertions(+), 10 deletions(-) create mode 100644 test/jdk/java/text/Format/MessageFormat/MaxArgumentIndexTest.java create mode 100644 test/jdk/java/text/Format/MessageFormat/SerializationTest.java diff --git a/src/java.base/share/classes/java/text/MessageFormat.java b/src/java.base/share/classes/java/text/MessageFormat.java index 28d1474ad71..1b32324ec38 100644 --- a/src/java.base/share/classes/java/text/MessageFormat.java +++ b/src/java.base/share/classes/java/text/MessageFormat.java @@ -41,6 +41,7 @@ import java.io.InvalidObjectException; import java.io.IOException; import java.io.ObjectInputStream; +import java.io.ObjectStreamException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; @@ -1008,6 +1009,8 @@ public Object[] parse(String source, ParsePosition pos) { maximumArgumentNumber = argumentNumbers[i]; } } + + // Constructors/applyPattern ensure that resultArray.length < MAX_ARGUMENT_INDEX Object[] resultArray = new Object[maximumArgumentNumber + 1]; int patternOffset = 0; @@ -1260,6 +1263,9 @@ protected Object readResolve() throws InvalidObjectException { * @serial */ private int[] argumentNumbers = new int[INITIAL_FORMATS]; + // Implementation limit for ArgumentIndex pattern element. Valid indices must + // be less than this value + private static final int MAX_ARGUMENT_INDEX = 10000; /** * One less than the number of entries in {@code offsets}. Can also be thought of @@ -1484,6 +1490,11 @@ private void makeFormat(int position, int offsetNumber, + argumentNumber); } + if (argumentNumber >= MAX_ARGUMENT_INDEX) { + throw new IllegalArgumentException( + argumentNumber + " exceeds the ArgumentIndex implementation limit"); + } + // resize format information arrays if necessary if (offsetNumber >= formats.length) { int newLength = formats.length * 2; @@ -1631,24 +1642,53 @@ private static final void copyAndFixQuotes(String source, int start, int end, */ @java.io.Serial private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - in.defaultReadObject(); - boolean isValid = maxOffset >= -1 - && formats.length > maxOffset - && offsets.length > maxOffset - && argumentNumbers.length > maxOffset; + ObjectInputStream.GetField fields = in.readFields(); + if (fields.defaulted("argumentNumbers") || fields.defaulted("offsets") + || fields.defaulted("formats") || fields.defaulted("locale") + || fields.defaulted("pattern") || fields.defaulted("maxOffset")){ + throw new InvalidObjectException("Stream has missing data"); + } + + locale = (Locale) fields.get("locale", null); + String patt = (String) fields.get("pattern", null); + int maxOff = fields.get("maxOffset", -2); + int[] argNums = ((int[]) fields.get("argumentNumbers", null)).clone(); + int[] offs = ((int[]) fields.get("offsets", null)).clone(); + Format[] fmts = ((Format[]) fields.get("formats", null)).clone(); + + // Check arrays/maxOffset have correct value/length + boolean isValid = maxOff >= -1 && argNums.length > maxOff + && offs.length > maxOff && fmts.length > maxOff; + + // Check the correctness of arguments and offsets if (isValid) { - int lastOffset = pattern.length() + 1; - for (int i = maxOffset; i >= 0; --i) { - if ((offsets[i] < 0) || (offsets[i] > lastOffset)) { + int lastOffset = patt.length() + 1; + for (int i = maxOff; i >= 0; --i) { + if (argNums[i] < 0 || argNums[i] >= MAX_ARGUMENT_INDEX + || offs[i] < 0 || offs[i] > lastOffset) { isValid = false; break; } else { - lastOffset = offsets[i]; + lastOffset = offs[i]; } } } + if (!isValid) { - throw new InvalidObjectException("Could not reconstruct MessageFormat from corrupt stream."); + throw new InvalidObjectException("Stream has invalid data"); } + maxOffset = maxOff; + pattern = patt; + offsets = offs; + formats = fmts; + argumentNumbers = argNums; + } + + /** + * Serialization without data not supported for this class. + */ + @java.io.Serial + private void readObjectNoData() throws ObjectStreamException { + throw new InvalidObjectException("Deserialized MessageFormat objects need data"); } } diff --git a/test/jdk/java/text/Format/MessageFormat/MaxArgumentIndexTest.java b/test/jdk/java/text/Format/MessageFormat/MaxArgumentIndexTest.java new file mode 100644 index 00000000000..e12dabb6383 --- /dev/null +++ b/test/jdk/java/text/Format/MessageFormat/MaxArgumentIndexTest.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8331446 + * @summary Enforce the MAX_ARGUMENT_INDEX(10,000) implementation limit for the + * ArgumentIndex element in the MessageFormat pattern syntax. This + * should be checked during construction/applyPattern/readObject and should effectively + * prevent parse/format from being invoked with values over the limit. + * @run junit MaxArgumentIndexTest + */ + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class MaxArgumentIndexTest { + + // A MessageFormat pattern that contains an ArgumentIndex value + // which violates this implementation's limit: MAX_ARGUMENT_INDEX(10,000) + // As this check is exclusive, 10,000 will violate the limit + private static final String VIOLATES_MAX_ARGUMENT_INDEX = "{10000}"; + + // Check String constructor enforces the limit + @Test + public void constructorTest() { + assertThrows(IllegalArgumentException.class, + () -> new MessageFormat(VIOLATES_MAX_ARGUMENT_INDEX)); + } + + // Check String, Locale constructor enforces the limit + @ParameterizedTest + @MethodSource + public void constructorWithLocaleTest(Locale locale) { + assertThrows(IllegalArgumentException.class, + () -> new MessageFormat(VIOLATES_MAX_ARGUMENT_INDEX, locale)); + } + + // Provide some basic common locale values + private static Stream constructorWithLocaleTest() { + return Stream.of(null, Locale.US, Locale.ROOT); + } + + // Edge case: Test a locale dependent subformat (with null locale) with a + // violating ArgumentIndex. In this instance, the violating ArgumentIndex + // will be caught and IAE thrown instead of the NPE + @Test + public void localeDependentSubFormatTest() { + assertThrows(IllegalArgumentException.class, + () -> new MessageFormat("{10000,number,short}", null)); + // For reference + assertThrows(NullPointerException.class, + () -> new MessageFormat("{999,number,short}", null)); + } + + // Check that the static format method enforces the limit + @Test + public void staticFormatTest() { + assertThrows(IllegalArgumentException.class, + () -> MessageFormat.format(VIOLATES_MAX_ARGUMENT_INDEX, new Object[]{1})); + } + + // Check that applyPattern(String) enforces the limit + @Test + public void applyPatternTest() { + MessageFormat mf = new MessageFormat(""); + assertThrows(IllegalArgumentException.class, + () -> mf.applyPattern(VIOLATES_MAX_ARGUMENT_INDEX)); + } +} diff --git a/test/jdk/java/text/Format/MessageFormat/SerializationTest.java b/test/jdk/java/text/Format/MessageFormat/SerializationTest.java new file mode 100644 index 00000000000..9191c5caef3 --- /dev/null +++ b/test/jdk/java/text/Format/MessageFormat/SerializationTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8331446 + * @summary Check correctness of deserialization + * @run junit SerializationTest + */ + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class SerializationTest { + + // Ensure basic correctness of serialization round trip + @ParameterizedTest + @MethodSource + public void serializationRoundTrip(MessageFormat expectedMf) + throws IOException, ClassNotFoundException { + byte[] bytes = ser(expectedMf); + MessageFormat actualMf = (MessageFormat) deSer(bytes); + assertEquals(expectedMf, actualMf); + } + + // Various valid MessageFormats + private static Stream serializationRoundTrip() { + return Stream.of( + // basic pattern + new MessageFormat("{0} foo"), + // Multiple arguments + new MessageFormat("{0} {1} foo"), + // duplicate arguments + new MessageFormat("{0} {0} {1} foo"), + // Non-ascending arguments + new MessageFormat("{1} {0} foo"), + // With locale + new MessageFormat("{1} {0} foo", Locale.UK), + // With null locale. (NPE not thrown, if no format defined) + new MessageFormat("{1} {0} foo", null), + // With formats + new MessageFormat("{0,number,short} {0} {1,date,long} foo") + ); + } + + // Utility method to serialize + private static byte[] ser(Object obj) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new + ByteArrayOutputStream(); + ObjectOutputStream oos = new + ObjectOutputStream(byteArrayOutputStream); + oos.writeObject(obj); + return byteArrayOutputStream.toByteArray(); + } + + // Utility method to deserialize + private static Object deSer(byte[] bytes) throws + IOException, ClassNotFoundException { + ByteArrayInputStream byteArrayInputStream = new + ByteArrayInputStream(bytes); + ObjectInputStream ois = new + ObjectInputStream(byteArrayInputStream); + return ois.readObject(); + } +} From fb2af1ce8fe704398e3dc66d7a049d8e50631c6c Mon Sep 17 00:00:00 2001 From: Andrew John Hughes Date: Mon, 7 Oct 2024 20:53:40 +0100 Subject: [PATCH 6/7] 8341674: [21u] Remove designator DEFAULT_PROMOTED_VERSION_PRE=ea for release 21.0.5 Reviewed-by: clanger --- make/conf/version-numbers.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make/conf/version-numbers.conf b/make/conf/version-numbers.conf index e8c9ad9e308..4497e1526bf 100644 --- a/make/conf/version-numbers.conf +++ b/make/conf/version-numbers.conf @@ -39,4 +39,4 @@ DEFAULT_VERSION_CLASSFILE_MINOR=0 DEFAULT_VERSION_DOCS_API_SINCE=11 DEFAULT_ACCEPTABLE_BOOT_VERSIONS="20 21" DEFAULT_JDK_SOURCE_TARGET_VERSION=21 -DEFAULT_PROMOTED_VERSION_PRE=ea +DEFAULT_PROMOTED_VERSION_PRE= From 9f9de26fc5246ba018ec4b9e2cc040fadba00b41 Mon Sep 17 00:00:00 2001 From: Christoph Langer Date: Fri, 11 Oct 2024 21:37:47 +0200 Subject: [PATCH 7/7] 8341989: [21u] Back out JDK-8327501 and JDK-8328366 Reviewed-by: goetz --- .../share/classes/java/util/concurrent/ForkJoinPool.java | 4 +++- test/jdk/java/util/concurrent/tck/ForkJoinPool9Test.java | 8 -------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java index 8aafda5312e..5e698b1540f 100644 --- a/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java +++ b/src/java.base/share/classes/java/util/concurrent/ForkJoinPool.java @@ -981,7 +981,9 @@ public final ForkJoinWorkerThread newThread(ForkJoinPool pool) { boolean isCommon = (pool.workerNamePrefix == null); @SuppressWarnings("removal") SecurityManager sm = System.getSecurityManager(); - if (sm != null && isCommon) + if (sm == null) + return new ForkJoinWorkerThread(null, pool, true, false); + else if (isCommon) return newCommonWithACC(pool); else return newRegularWithACC(pool); diff --git a/test/jdk/java/util/concurrent/tck/ForkJoinPool9Test.java b/test/jdk/java/util/concurrent/tck/ForkJoinPool9Test.java index a87aa7b916b..266c2a036fe 100644 --- a/test/jdk/java/util/concurrent/tck/ForkJoinPool9Test.java +++ b/test/jdk/java/util/concurrent/tck/ForkJoinPool9Test.java @@ -79,9 +79,6 @@ public void testCommonPoolThreadContextClassLoader() throws Throwable { assertSame(ForkJoinPool.commonPool(), ForkJoinTask.getPool()); Thread currentThread = Thread.currentThread(); - ClassLoader preexistingContextClassLoader = - currentThread.getContextClassLoader(); - Stream.of(systemClassLoader, null).forEach(cl -> { if (randomBoolean()) // should always be permitted, without effect @@ -98,11 +95,6 @@ public void testCommonPoolThreadContextClassLoader() throws Throwable { () -> System.getProperty("foo"), () -> currentThread.setContextClassLoader( classLoaderDistinctFromSystemClassLoader)); - else { - currentThread.setContextClassLoader(classLoaderDistinctFromSystemClassLoader); - assertSame(currentThread.getContextClassLoader(), classLoaderDistinctFromSystemClassLoader); - currentThread.setContextClassLoader(preexistingContextClassLoader); - } // TODO ? // if (haveSecurityManager // && Thread.currentThread().getClass().getSimpleName()