From 217b9fdf056f4887230f60fdf60b036861699cd8 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Tue, 1 Oct 2024 13:34:57 +0000 Subject: [PATCH 01/16] 8341057: Add 2 SSL.com TLS roots Reviewed-by: mbaesken Backport-of: 824a297aae15ba16cf6d7aded4b95fc9d6bf55e5 --- make/data/cacerts/ssltlsrootecc2022 | 21 ++++++++++ make/data/cacerts/ssltlsrootrsa2022 | 39 +++++++++++++++++++ .../certification/CAInterop.java | 29 ++++++++++++++ .../security/lib/cacerts/VerifyCACerts.java | 13 ++++--- 4 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 make/data/cacerts/ssltlsrootecc2022 create mode 100644 make/data/cacerts/ssltlsrootrsa2022 diff --git a/make/data/cacerts/ssltlsrootecc2022 b/make/data/cacerts/ssltlsrootecc2022 new file mode 100644 index 00000000000..706e6aefb4e --- /dev/null +++ b/make/data/cacerts/ssltlsrootecc2022 @@ -0,0 +1,21 @@ +Owner: CN=SSL.com TLS ECC Root CA 2022, O=SSL Corporation, C=US +Issuer: CN=SSL.com TLS ECC Root CA 2022, O=SSL Corporation, C=US +Serial number: 1403f5abfb378b17405be243b2a5d1c4 +Valid from: Thu Aug 25 16:33:48 GMT 2022 until: Sun Aug 19 16:33:47 GMT 2046 +Signature algorithm name: SHA384withECDSA +Subject Public Key Algorithm: 384-bit EC (secp384r1) key +Version: 3 +-----BEGIN CERTIFICATE----- +MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw +CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT +U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 +MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh +dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG +ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm +acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN +SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME +GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW +uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp +15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN +b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== +-----END CERTIFICATE----- diff --git a/make/data/cacerts/ssltlsrootrsa2022 b/make/data/cacerts/ssltlsrootrsa2022 new file mode 100644 index 00000000000..ad456b0b5f4 --- /dev/null +++ b/make/data/cacerts/ssltlsrootrsa2022 @@ -0,0 +1,39 @@ +Owner: CN=SSL.com TLS RSA Root CA 2022, O=SSL Corporation, C=US +Issuer: CN=SSL.com TLS RSA Root CA 2022, O=SSL Corporation, C=US +Serial number: 6fbedaad73bd0840e28b4dbed4f75b91 +Valid from: Thu Aug 25 16:34:22 GMT 2022 until: Sun Aug 19 16:34:21 GMT 2046 +Signature algorithm name: SHA256withRSA +Subject Public Key Algorithm: 4096-bit RSA key +Version: 3 +-----BEGIN CERTIFICATE----- +MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO +MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD +DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX +DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw +b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC +AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP +L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY +t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins +S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 +PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO +L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 +R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w +dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS ++YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS +d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG +AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f +gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j +BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z +NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt +hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM +QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf +R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ +DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW +P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy +lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq +bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w +AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q +r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji +Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU +98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= +-----END CERTIFICATE----- diff --git a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java index 32fba48cc3d..33f777d6490 100644 --- a/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java +++ b/test/jdk/security/infra/java/security/cert/CertPathValidator/certification/CAInterop.java @@ -483,6 +483,28 @@ * @run main/othervm/manual -Djava.security.debug=certpath CAInterop globalsigne46 CRL */ +/* + * @test id=ssltlsrootecc2022 + * @bug 8341057 + * @summary Interoperability tests with SSL TLS 2022 root CAs + * @library /test/lib + * @build jtreg.SkippedException ValidatePathWithURL CAInterop + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp CAInterop ssltlsrootecc2022 DEFAULT + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp -Dcom.sun.security.ocsp.useget=false CAInterop ssltlsrootecc2022 DEFAULT + * @run main/othervm/manual -Djava.security.debug=certpath CAInterop ssltlsrootecc2022 CRL + */ + +/* + * @test id=ssltlsrootrsa2022 + * @bug 8341057 + * @summary Interoperability tests with SSL TLS 2022 root CAs + * @library /test/lib + * @build jtreg.SkippedException ValidatePathWithURL CAInterop + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp CAInterop ssltlsrootrsa2022 DEFAULT + * @run main/othervm/manual -Djava.security.debug=certpath,ocsp -Dcom.sun.security.ocsp.useget=false CAInterop ssltlsrootrsa2022 DEFAULT + * @run main/othervm/manual -Djava.security.debug=certpath CAInterop ssltlsrootrsa2022 CRL + */ + /** * Collection of certificate validation tests for interoperability with external CAs. * These tests are marked as manual as they depend on external infrastructure and may fail @@ -661,6 +683,13 @@ private CATestURLs getTestURLs(String alias) { return new CATestURLs("https://valid.e46.roots.globalsign.com", "https://revoked.e46.roots.globalsign.com"); + case "ssltlsrootecc2022": + return new CATestURLs("https://test-root-2022-ecc.ssl.com", + "https://revoked-root-2022-ecc.ssl.com"); + case "ssltlsrootrsa2022": + return new CATestURLs("https://test-root-2022-rsa.ssl.com", + "https://revoked-root-2022-rsa.ssl.com"); + default: throw new RuntimeException("No test setup found for: " + alias); } } diff --git a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java index 3c4a50c03ed..a0e02b32319 100644 --- a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java +++ b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java @@ -19,17 +19,16 @@ * 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 8189131 8198240 8191844 8189949 8191031 8196141 8204923 8195774 8199779 * 8209452 8209506 8210432 8195793 8216577 8222089 8222133 8222137 8222136 * 8223499 8225392 8232019 8234245 8233223 8225068 8225069 8243321 8243320 * 8243559 8225072 8258630 8259312 8256421 8225081 8225082 8225083 8245654 * 8305975 8304760 8307134 8295894 8314960 8317373 8317374 8318759 8319187 - * 8321408 8316138 + * 8321408 8316138 8341057 * @summary Check root CA entries in cacerts file */ import java.io.ByteArrayInputStream; @@ -48,12 +47,12 @@ public class VerifyCACerts { + File.separator + "security" + File.separator + "cacerts"; // The numbers of certs now. - private static final int COUNT = 110; + private static final int COUNT = 112; // SHA-256 of cacerts, can be generated with // shasum -a 256 cacerts | sed -e 's/../&:/g' | tr '[:lower:]' '[:upper:]' | cut -c1-95 private static final String CHECKSUM - = "C1:68:B4:AC:51:BF:B5:C6:FD:20:69:17:E1:AF:E4:5B:01:9B:AA:3F:C3:9A:80:A8:51:53:74:2C:A2:04:B0:FF"; + = "8F:E0:6F:7F:21:59:33:A6:43:F3:48:FD:A3:4A:8E:28:35:AA:DD:6E:A5:43:56:F1:28:34:48:DF:5C:D2:7C:72"; // map of cert alias to SHA-256 fingerprint @SuppressWarnings("serial") @@ -279,6 +278,10 @@ public class VerifyCACerts { "4F:A3:12:6D:8D:3A:11:D1:C4:85:5A:4F:80:7C:BA:D6:CF:91:9D:3A:5A:88:B0:3B:EA:2C:63:72:D9:3C:40:C9"); put("globalsigne46 [jdk]", "CB:B9:C4:4D:84:B8:04:3E:10:50:EA:31:A6:9F:51:49:55:D7:BF:D2:E2:C6:B4:93:01:01:9A:D6:1D:9F:50:58"); + put("ssltlsrootecc2022 [jdk]", + "C3:2F:FD:9F:46:F9:36:D1:6C:36:73:99:09:59:43:4B:9A:D6:0A:AF:BB:9E:7C:F3:36:54:F1:44:CC:1B:A1:43"); + put("ssltlsrootrsa2022 [jdk]", + "8F:AF:7D:2E:2C:B4:70:9B:B8:E0:B3:36:66:BF:75:A5:DD:45:B5:DE:48:0F:8E:A8:D4:BF:E6:BE:BC:17:F2:ED"); } }; From 82c330b46498e78c67c1905535106c97536e4914 Mon Sep 17 00:00:00 2001 From: Goetz Lindenmaier Date: Tue, 1 Oct 2024 13:37:14 +0000 Subject: [PATCH 02/16] 8341059: Change Entrust TLS distrust date to November 12, 2024 Backport-of: eced83e13090748218ab3dac78f6ff1bddf2b158 --- .../sun/security/validator/CADistrustPolicy.java | 2 +- .../sun/security/validator/EntrustTLSPolicy.java | 8 ++++---- src/java.base/share/conf/security/java.security | 2 +- .../ssl/X509TrustManagerImpl/Entrust/Distrust.java | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java b/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java index e943bcce7ba..39aecc27ecd 100644 --- a/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java @@ -57,7 +57,7 @@ void checkDistrust(String variant, X509Certificate[] chain) /** * Distrust TLS Server certificates anchored by an Entrust root CA and - * issued after October 31, 2024. If enabled, this policy is currently + * issued after November 11, 2024. If enabled, this policy is currently * enforced by the PKIX and SunX509 TrustManager implementations * of the SunJSSE provider implementation. */ diff --git a/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java index 730e5684fe6..a19c2e5039d 100644 --- a/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/EntrustTLSPolicy.java @@ -88,8 +88,8 @@ final class EntrustTLSPolicy { // Any TLS Server certificate that is anchored by one of the Entrust // roots above and is issued after this date will be distrusted. - private static final LocalDate OCTOBER_31_2024 = - LocalDate.of(2024, Month.OCTOBER, 31); + private static final LocalDate NOVEMBER_11_2024 = + LocalDate.of(2024, Month.NOVEMBER, 11); /** * This method assumes the eeCert is a TLS Server Cert and chains back to @@ -111,8 +111,8 @@ static void checkDistrust(X509Certificate[] chain) Date notBefore = chain[0].getNotBefore(); LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(), ZoneOffset.UTC); - // reject if certificate is issued after October 31, 2024 - checkNotBefore(ldNotBefore, OCTOBER_31_2024, anchor); + // reject if certificate is issued after November 11, 2024 + checkNotBefore(ldNotBefore, NOVEMBER_11_2024, anchor); } } diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index e3a42677744..6f3f5448b93 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1288,7 +1288,7 @@ jdk.sasl.disabledMechanisms= # Distrust after December 31, 2019. # # ENTRUST_TLS : Distrust TLS Server certificates anchored by -# an Entrust root CA and issued after October 31, 2024. +# an Entrust root CA and issued after November 11, 2024. # # Leading and trailing whitespace surrounding each value are ignored. # Unknown values are ignored. If the property is commented out or set to the diff --git a/test/jdk/sun/security/ssl/X509TrustManagerImpl/Entrust/Distrust.java b/test/jdk/sun/security/ssl/X509TrustManagerImpl/Entrust/Distrust.java index a9a47bf834c..1c1fcee3609 100644 --- a/test/jdk/sun/security/ssl/X509TrustManagerImpl/Entrust/Distrust.java +++ b/test/jdk/sun/security/ssl/X509TrustManagerImpl/Entrust/Distrust.java @@ -35,7 +35,7 @@ /** * @test - * @bug 8337664 + * @bug 8337664 8341059 * @summary Check that TLS Server certificates chaining back to distrusted * Entrust roots are invalid * @library /test/lib @@ -59,14 +59,14 @@ public class Distrust { "affirmtrustpremiumca", "affirmtrustpremiumeccca" }; // A date that is after the restrictions take effect - private static final Date NOVEMBER_1_2024 = - Date.from(LocalDate.of(2024, 11, 1) + private static final Date NOVEMBER_12_2024 = + Date.from(LocalDate.of(2024, 11, 12) .atStartOfDay(ZoneOffset.UTC) .toInstant()); // A date that is a second before the restrictions take effect - private static final Date BEFORE_NOVEMBER_1_2024 = - Date.from(LocalDate.of(2024, 11, 1) + private static final Date BEFORE_NOVEMBER_12_2024 = + Date.from(LocalDate.of(2024, 11, 12) .atStartOfDay(ZoneOffset.UTC) .minusSeconds(1) .toInstant()); @@ -84,7 +84,7 @@ public static void main(String[] args) throws Exception { Security.setProperty("jdk.security.caDistrustPolicies", ""); } - Date notBefore = before ? BEFORE_NOVEMBER_1_2024 : NOVEMBER_1_2024; + Date notBefore = before ? BEFORE_NOVEMBER_12_2024 : NOVEMBER_12_2024; X509TrustManager pkixTM = getTMF("PKIX", null); X509TrustManager sunX509TM = getTMF("SunX509", null); From 240b2962343493f824b5595432ae6cdd959666f0 Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Mon, 30 Sep 2024 17:18:02 +0200 Subject: [PATCH 03/16] 8307383: Enhance DTLS connections Reviewed-by: mbaesken, andrew Backport-of: 362dbbaa952b3d4a5270c6bfae879a12e9bdf4d1 --- .../classes/sun/security/ssl/ClientHello.java | 5 +- .../sun/security/ssl/DTLSInputRecord.java | 145 ++++++++++++++++-- .../security/ssl/ServerHandshakeContext.java | 3 +- .../sun/security/ssl/TransportContext.java | 8 +- .../javax/net/ssl/DTLS/InvalidRecords.java | 32 +++- .../jdk/javax/net/ssl/TLSCommon/MFLNTest.java | 12 +- 6 files changed, 186 insertions(+), 19 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 4bc6bbe1f94..a5d0ac94b33 100644 --- a/src/java.base/share/classes/sun/security/ssl/ClientHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ClientHello.java @@ -218,8 +218,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 } @@ -1390,6 +1388,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 0aee1f4035a..14e8db6ed71 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, 2020, 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 @@ -28,13 +28,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; import javax.crypto.BadPaddingException; import javax.net.ssl.SSLException; import javax.net.ssl.SSLProtocolException; @@ -46,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; @@ -544,6 +549,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 @@ -647,10 +673,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); @@ -783,6 +816,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 cb2a812430f..01c9e6b1ec3 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, 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 be3f480ae1f..4c1b1c2df82 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 @@ -138,6 +138,12 @@ private TransportContext(SSLContextImpl sslContext, SSLTransport transport, this.acc = AccessController.getContext(); this.consumers = new HashMap<>(); + + if (inputRecord instanceof DTLSInputRecord) { + DTLSInputRecord dtlsInputRecord = (DTLSInputRecord)inputRecord; + 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 53b50bd7464..e0991db0314 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; import jdk.test.lib.security.SecurityUtils; @@ -76,11 +77,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 445e79ea99250a77ca38223710ca7e7fef0b6309 Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Fri, 27 Sep 2024 15:25:46 -0700 Subject: [PATCH 04/16] 8263031: HttpClient throws Exception if it receives a Push Promise that is too large Reviewed-by: mbalao Backport-of: 4d2cd26ab5092ad0a169e4239164a869a4255bd3 --- .../internal/net/http/Http2Connection.java | 90 ++++-- .../http2/PushPromiseContinuation.java | 303 ++++++++++++++++++ .../http2/server/Http2TestExchangeImpl.java | 16 +- .../server/Http2TestServerConnection.java | 59 +++- 4 files changed, 428 insertions(+), 40 deletions(-) create mode 100644 test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java 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 7cf7731df72..c39c3326d95 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, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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 @@ -243,6 +243,16 @@ void markPrefaceSent() { } } + private final class PushContinuationState { + final HeaderDecoder pushContDecoder; + final PushPromiseFrame pushContFrame; + + PushContinuationState(HeaderDecoder pushContDecoder, PushPromiseFrame pushContFrame) { + this.pushContDecoder = pushContDecoder; + this.pushContFrame = pushContFrame; + } + } + volatile boolean closed; //------------------------------------- @@ -263,6 +273,7 @@ void markPrefaceSent() { private final Decoder hpackIn; final SettingsFrame clientSettings; private volatile SettingsFrame serverSettings; + private volatile PushContinuationState pushContinuationState; private final String key; // for HttpClientImpl.connections map private final FramesDecoder framesDecoder; private final FramesEncoder framesEncoder = new FramesEncoder(); @@ -787,25 +798,44 @@ void processFrame(Http2Frame frame) throws IOException { } return; } - if (frame instanceof PushPromiseFrame) { - PushPromiseFrame pp = (PushPromiseFrame)frame; - try { - handlePushPromise(stream, pp); - } catch (UncheckedIOException e) { - protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage()); - return; - } - } else if (frame instanceof HeaderFrame) { - // decode headers (or continuation) - try { - decodeHeaders((HeaderFrame) frame, stream.rspHeadersConsumer()); - } catch (UncheckedIOException e) { - protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage()); + + // While push frame is not null, the only acceptable frame on this + // stream is a Continuation frame + if (pushContinuationState != null) { + if (frame instanceof ContinuationFrame) { + try { + handlePushContinuation(stream, (ContinuationFrame)frame); + } catch (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); return; } - stream.incoming(frame); } else { - stream.incoming(frame); + if (frame instanceof PushPromiseFrame) { + try { + handlePushPromise(stream, (PushPromiseFrame)frame); + } catch (UncheckedIOException e) { + protocolError(ErrorFrame.PROTOCOL_ERROR, e.getMessage()); + return; + } + } else if (frame instanceof HeaderFrame) { + // decode headers + try { + decodeHeaders((HeaderFrame)frame, stream.rspHeadersConsumer()); + } catch (UncheckedIOException e) { + debug.log("Error decoding headers: " + e.getMessage(), e); + protocolError(ErrorFrame.PROTOCOL_ERROR, e.getMessage()); + return; + } + stream.incoming(frame); + } else { + stream.incoming(frame); + } } } } @@ -836,11 +866,34 @@ private void handlePushPromise(Stream parent, PushPromiseFrame pp) { // always decode the headers as they may affect connection-level HPACK // decoding state + assert pushContinuationState == null; HeaderDecoder decoder = new HeaderDecoder(); decodeHeaders(pp, decoder); + int promisedStreamid = pp.getPromisedStream(); + if (pp.endHeaders()) { + completePushPromise(promisedStreamid, parent, decoder.headers()); + } else { + pushContinuationState = new PushContinuationState(decoder, pp); + } + } + private void handlePushContinuation(Stream parent, ContinuationFrame cf) + throws IOException { + var pcs = pushContinuationState; + decodeHeaders(cf, pcs.pushContDecoder); + // if all continuations are sent, set pushWithContinuation to null + if (cf.endHeaders()) { + completePushPromise(pcs.pushContFrame.getPromisedStream(), parent, + pcs.pushContDecoder.headers()); + pushContinuationState = null; + } + } + + private void completePushPromise(int promisedStreamid, Stream parent, HttpHeaders headers) + throws IOException { + // Perhaps the following checks could be moved to handlePushPromise() + // to reset the PushPromise stream earlier? HttpRequestImpl parentReq = parent.request; - int promisedStreamid = pp.getPromisedStream(); if (promisedStreamid != nextPushStream) { resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR); return; @@ -851,7 +904,6 @@ private void handlePushPromise(Stream parent, PushPromiseFrame pp) nextPushStream += 2; } - HttpHeaders headers = decoder.headers(); HttpRequestImpl pushReq = HttpRequestImpl.createPushRequest(parentReq, headers); Exchange pushExch = new Exchange<>(pushReq, parent.exchange.multi); Stream.PushedStream pushStream = createPushStream(parent, pushExch); diff --git a/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java b/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java new file mode 100644 index 00000000000..49e76b7c274 --- /dev/null +++ b/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java @@ -0,0 +1,303 @@ +/* + * Copyright (c) 2022, 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 8263031 + * @summary Tests that the HttpClient can correctly receive a Push Promise + * Frame with the END_HEADERS flag unset followed by one or more + * Continuation Frames. + * @library /test/lib server + * @build jdk.test.lib.net.SimpleSSLContext + * @modules java.base/sun.net.www.http + * java.net.http/jdk.internal.net.http.common + * java.net.http/jdk.internal.net.http.frame + * java.net.http/jdk.internal.net.http.hpack + * @run testng/othervm PushPromiseContinuation + */ + + +import jdk.internal.net.http.common.HttpHeadersBuilder; +import jdk.internal.net.http.frame.ContinuationFrame; +import jdk.internal.net.http.frame.HeaderFrame; +import org.testng.TestException; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import javax.net.ssl.SSLSession; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.BiPredicate; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.testng.Assert.assertEquals; + +public class PushPromiseContinuation { + + static volatile HttpHeaders testHeaders; + static volatile HttpHeadersBuilder testHeadersBuilder; + static volatile int continuationCount; + static final String mainPromiseBody = "Main Promise Body"; + static final String mainResponseBody = "Main Response Body"; + Http2TestServer server; + URI uri; + + // Set up simple client-side push promise handler + ConcurrentMap>> pushPromiseMap = new ConcurrentHashMap<>(); + HttpResponse.PushPromiseHandler pph = (initial, pushRequest, acceptor) -> { + HttpResponse.BodyHandler s = HttpResponse.BodyHandlers.ofString(UTF_8); + pushPromiseMap.put(pushRequest, acceptor.apply(s)); + }; + + @BeforeMethod + public void beforeMethod() { + pushPromiseMap = new ConcurrentHashMap<>(); + } + + @BeforeTest + public void setup() throws Exception { + server = new Http2TestServer(false, 0); + server.addHandler(new ServerPushHandler(), "/"); + + // Need to have a custom exchange supplier to manage the server's push + // promise with continuation flow + server.setExchangeSupplier(Http2LPPTestExchangeImpl::new); + + System.err.println("PushPromiseContinuation: Server listening on port " + server.getAddress().getPort()); + server.start(); + int port = server.getAddress().getPort(); + uri = new URI("http://localhost:" + port + "/"); + } + + @AfterTest + public void teardown() { + pushPromiseMap = null; + server.stop(); + } + + /** + * Tests that when the client receives PushPromise Frame with the END_HEADERS + * flag set to 0x0 and subsequently receives a continuation frame, no exception + * is thrown and all headers from the PushPromise and Continuation Frames sent + * by the server arrive at the client. + */ + @Test + public void testOneContinuation() { + continuationCount = 1; + HttpClient client = HttpClient.newHttpClient(); + + // Carry out request + HttpRequest hreq = HttpRequest.newBuilder(uri).version(HttpClient.Version.HTTP_2).GET().build(); + CompletableFuture> cf = + client.sendAsync(hreq, HttpResponse.BodyHandlers.ofString(UTF_8), pph); + HttpResponse resp = cf.join(); + + // Verify results + verify(resp); + } + + /** + * Same as above, but tests for the case where two Continuation Frames are sent + * with the END_HEADERS flag set only on the last frame. + */ + @Test + public void testTwoContinuations() { + continuationCount = 2; + HttpClient client = HttpClient.newHttpClient(); + + // Carry out request + HttpRequest hreq = HttpRequest.newBuilder(uri).version(HttpClient.Version.HTTP_2).GET().build(); + CompletableFuture> cf = + client.sendAsync(hreq, HttpResponse.BodyHandlers.ofString(UTF_8), pph); + HttpResponse resp = cf.join(); + + // Verify results + verify(resp); + } + + @Test + public void testThreeContinuations() { + continuationCount = 3; + HttpClient client = HttpClient.newHttpClient(); + + // Carry out request + HttpRequest hreq = HttpRequest.newBuilder(uri).version(HttpClient.Version.HTTP_2).GET().build(); + CompletableFuture> cf = + client.sendAsync(hreq, HttpResponse.BodyHandlers.ofString(UTF_8), pph); + HttpResponse resp = cf.join(); + + // Verify results + verify(resp); + } + + private void verify(HttpResponse resp) { + assertEquals(resp.statusCode(), 200); + assertEquals(resp.body(), mainResponseBody); + if (pushPromiseMap.size() > 1) { + System.err.println(pushPromiseMap.entrySet()); + throw new TestException("Results map size is greater than 1"); + } else { + // This will only iterate once + for (HttpRequest r : pushPromiseMap.keySet()) { + HttpResponse serverPushResp = pushPromiseMap.get(r).join(); + // Received headers should be the same as the combined PushPromise + // frame headers combined with the Continuation frame headers + assertEquals(testHeaders, r.headers()); + // Check status code and push promise body are as expected + assertEquals(serverPushResp.statusCode(), 200); + assertEquals(serverPushResp.body(), mainPromiseBody); + } + } + } + + static class Http2LPPTestExchangeImpl extends Http2TestExchangeImpl { + + HttpHeadersBuilder pushPromiseHeadersBuilder; + List cfs; + + Http2LPPTestExchangeImpl(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); + } + + private void setPushHeaders(String name, String value) { + pushPromiseHeadersBuilder.setHeader(name, value); + testHeadersBuilder.setHeader(name, value); + } + + private void assembleContinuations() { + for (int i = 0; i < continuationCount; i++) { + HttpHeadersBuilder builder = new HttpHeadersBuilder(); + for (int j = 0; j < 10; j++) { + String name = "x-cont-" + i + "-" + j; + builder.setHeader(name, "data_" + j); + testHeadersBuilder.setHeader(name, "data_" + j); + } + + ContinuationFrame cf = new ContinuationFrame(streamid, 0x0, conn.encodeHeaders(builder.build())); + // If this is the last Continuation Frame, set the END_HEADERS flag. + if (i >= continuationCount - 1) { + cf.setFlag(HeaderFrame.END_HEADERS); + } + cfs.add(cf); + } + } + + @Override + public void serverPush(URI uri, HttpHeaders headers, InputStream content) { + pushPromiseHeadersBuilder = new HttpHeadersBuilder(); + testHeadersBuilder = new HttpHeadersBuilder(); + cfs = new ArrayList<>(); + + setPushHeaders(":method", "GET"); + setPushHeaders(":scheme", uri.getScheme()); + setPushHeaders(":authority", uri.getAuthority()); + setPushHeaders(":path", uri.getPath()); + for (Map.Entry> entry : headers.map().entrySet()) { + for (String value : entry.getValue()) { + setPushHeaders(entry.getKey(), value); + } + } + + for (int i = 0; i < 10; i++) { + setPushHeaders("x-push-header-" + i, "data_" + i); + } + + // Create the Continuation Frame/s, done before Push Promise Frame for test purposes + // as testHeaders contains all headers used in all frames + assembleContinuations(); + + HttpHeaders pushPromiseHeaders = pushPromiseHeadersBuilder.build(); + testHeaders = testHeadersBuilder.build(); + // Create the Push Promise Frame + OutgoingPushPromise pp = new OutgoingPushPromise(streamid, uri, pushPromiseHeaders, content); + // Indicates to the client that a continuation should be expected + pp.setFlag(0x0); + + try { + // Schedule push promise and continuation for sending + conn.outputQ.put(pp); + System.err.println("Server: Scheduled a Push Promise to Send"); + for (ContinuationFrame cf : cfs) { + conn.outputQ.put(cf); + System.err.println("Server: Scheduled a Continuation to Send"); + } + } catch (IOException ex) { + System.err.println("Server: pushPromise exception: " + ex); + } + } + } + + static class ServerPushHandler implements Http2Handler { + + public void handle(Http2TestExchange exchange) throws IOException { + System.err.println("Server: handle " + exchange); + try (InputStream is = exchange.getRequestBody()) { + is.readAllBytes(); + } + + if (exchange.serverPushAllowed()) { + pushPromise(exchange); + } + + // response data for the main response + try (OutputStream os = exchange.getResponseBody()) { + byte[] bytes = mainResponseBody.getBytes(UTF_8); + exchange.sendResponseHeaders(200, bytes.length); + os.write(bytes); + } + } + + static final BiPredicate ACCEPT_ALL = (x, y) -> true; + + + private void pushPromise(Http2TestExchange exchange) throws IOException { + URI requestURI = exchange.getRequestURI(); + URI uri = requestURI.resolve("/promise"); + InputStream is = new ByteArrayInputStream(mainPromiseBody.getBytes(UTF_8)); + Map> map = new HashMap<>(); + map.put("x-promise", List.of("promise-header")); + HttpHeaders headers = HttpHeaders.of(map, ACCEPT_ALL); + exchange.serverPush(uri, headers, is); + System.err.println("Server: Push Promise complete"); + } + } +} diff --git a/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java index 7bb8ad57e45..38e22d3bc47 100644 --- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java +++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022, 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 @@ -21,19 +21,20 @@ * questions. */ +import jdk.internal.net.http.common.HttpHeadersBuilder; +import jdk.internal.net.http.frame.HeaderFrame; +import jdk.internal.net.http.frame.HeadersFrame; + +import javax.net.ssl.SSLSession; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.IOException; -import java.net.URI; import java.net.InetSocketAddress; +import java.net.URI; import java.net.http.HttpHeaders; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; -import javax.net.ssl.SSLSession; -import jdk.internal.net.http.common.HttpHeadersBuilder; -import jdk.internal.net.http.frame.HeaderFrame; -import jdk.internal.net.http.frame.HeadersFrame; public class Http2TestExchangeImpl implements Http2TestExchange { @@ -191,6 +192,7 @@ public void serverPush(URI uri, HttpHeaders headers, InputStream content) { } HttpHeaders combinedHeaders = headersBuilder.build(); OutgoingPushPromise pp = new OutgoingPushPromise(streamid, uri, combinedHeaders, content); + pp.setFlag(HeaderFrame.END_HEADERS); try { conn.outputQ.put(pp); diff --git a/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java b/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java index 5f0e83a1716..5cb5c2a0d0f 100644 --- a/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java +++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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 @@ -21,32 +21,63 @@ * questions. */ +import jdk.internal.net.http.common.HttpHeadersBuilder; +import jdk.internal.net.http.frame.DataFrame; +import jdk.internal.net.http.frame.ErrorFrame; +import jdk.internal.net.http.frame.FramesDecoder; +import jdk.internal.net.http.frame.FramesEncoder; +import jdk.internal.net.http.frame.GoAwayFrame; +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.PingFrame; +import jdk.internal.net.http.frame.PushPromiseFrame; +import jdk.internal.net.http.frame.ResetFrame; +import jdk.internal.net.http.frame.SettingsFrame; +import jdk.internal.net.http.frame.WindowUpdateFrame; +import jdk.internal.net.http.hpack.Decoder; +import jdk.internal.net.http.hpack.DecodingCallback; +import jdk.internal.net.http.hpack.Encoder; +import sun.net.www.http.ChunkedInputStream; +import sun.net.www.http.HttpClient; + +import javax.net.ssl.SNIHostName; +import javax.net.ssl.SNIMatcher; +import javax.net.ssl.SNIServerName; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.StandardConstants; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.Closeable; import java.io.IOException; -import java.io.UncheckedIOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.net.InetAddress; import java.net.Socket; import java.net.URI; -import java.net.InetAddress; -import javax.net.ssl.*; import java.net.URISyntaxException; import java.net.http.HttpHeaders; import java.nio.ByteBuffer; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.Random; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; import java.util.function.Consumer; -import jdk.internal.net.http.common.HttpHeadersBuilder; -import jdk.internal.net.http.frame.*; -import jdk.internal.net.http.hpack.Decoder; -import jdk.internal.net.http.hpack.DecodingCallback; -import jdk.internal.net.http.hpack.Encoder; -import sun.net.www.http.ChunkedInputStream; -import sun.net.www.http.HttpClient; + 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.HEADER_TABLE_SIZE; @@ -910,7 +941,7 @@ void writeLoop() { private void handlePush(OutgoingPushPromise op) throws IOException { int promisedStreamid = nextPushStreamId; PushPromiseFrame pp = new PushPromiseFrame(op.parentStream, - HeaderFrame.END_HEADERS, + op.getFlags(), promisedStreamid, encodeHeaders(op.headers), 0); From fe9c9c707b5555d24cb0ec4b2952aa5b056626b9 Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Fri, 27 Sep 2024 16:45:17 -0700 Subject: [PATCH 05/16] 8303965: java.net.http.HttpClient should reset the stream if response headers contain malformed header fields Reviewed-by: mbalao Backport-of: 466ffebcae1ee5817a83fdbc33f5ec3bd6de7e60 --- .../jdk/internal/net/http/Exchange.java | 4 +- .../internal/net/http/Http2Connection.java | 95 +++++-------------- .../classes/jdk/internal/net/http/Stream.java | 34 +++++-- .../net/http/common/HeaderDecoder.java | 48 ++++++++++ .../jdk/internal/net/http/common/Utils.java | 25 ++++- .../common/ValidatingHeadersConsumer.java | 89 +++++++++++++++++ .../net/httpclient/http2/BadHeadersTest.java | 35 ++++--- 7 files changed, 226 insertions(+), 104 deletions(-) create mode 100644 src/java.net.http/share/classes/jdk/internal/net/http/common/HeaderDecoder.java create mode 100644 src/java.net.http/share/classes/jdk/internal/net/http/common/ValidatingHeadersConsumer.java 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 e28a82f71a2..b501c2fe0bc 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, 2019, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -562,7 +562,7 @@ HttpResponse.BodySubscriber ignoreBody(HttpResponse.ResponseInfo hdrs) { this, e::drainLeftOverBytes) .thenCompose((Http2Connection c) -> { boolean cached = c.offerConnection(); - Stream s = c.getStream(1); + Stream s = c.getInitialStream(); if (s == null) { // s can be null if an exception occurred 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 c39c3326d95..6d03ca5ae98 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 @@ -53,12 +53,14 @@ import jdk.internal.net.http.HttpConnection.HttpPublisher; import jdk.internal.net.http.common.FlowTube; import jdk.internal.net.http.common.FlowTube.TubeSubscriber; +import jdk.internal.net.http.common.HeaderDecoder; import jdk.internal.net.http.common.HttpHeadersBuilder; 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.SequentialScheduler; import jdk.internal.net.http.common.Utils; +import jdk.internal.net.http.common.ValidatingHeadersConsumer; import jdk.internal.net.http.frame.ContinuationFrame; import jdk.internal.net.http.frame.DataFrame; import jdk.internal.net.http.frame.ErrorFrame; @@ -288,6 +290,7 @@ private final class PushContinuationState { final ConnectionWindowUpdateSender windowUpdater; private volatile Throwable cause; private volatile Supplier initial; + private volatile Stream initialStream; static final int DEFAULT_FRAME_SIZE = 16 * 1024; @@ -341,6 +344,7 @@ private Http2Connection(HttpConnection connection, Stream initialStream = createStream(exchange); boolean opened = initialStream.registerStream(1, true); + this.initialStream = initialStream; if (debug.on() && !opened) { debug.log("Initial stream was cancelled - but connection is maintained: " + "reset frame will need to be sent later"); @@ -770,7 +774,7 @@ void processFrame(Http2Frame frame) throws IOException { if (frame instanceof HeaderFrame) { // always decode the headers as they may affect // connection-level HPACK decoding state - DecodingCallback decoder = new ValidatingHeadersConsumer(); + DecodingCallback decoder = new ValidatingHeadersConsumer()::onDecoded; try { decodeHeaders((HeaderFrame) frame, decoder); } catch (UncheckedIOException e) { @@ -868,7 +872,7 @@ private void handlePushPromise(Stream parent, PushPromiseFrame pp) // decoding state assert pushContinuationState == null; HeaderDecoder decoder = new HeaderDecoder(); - decodeHeaders(pp, decoder); + decodeHeaders(pp, decoder::onDecoded); int promisedStreamid = pp.getPromisedStream(); if (pp.endHeaders()) { completePushPromise(promisedStreamid, parent, decoder.headers()); @@ -880,7 +884,7 @@ private void handlePushPromise(Stream parent, PushPromiseFrame pp) private void handlePushContinuation(Stream parent, ContinuationFrame cf) throws IOException { var pcs = pushContinuationState; - decodeHeaders(cf, pcs.pushContDecoder); + decodeHeaders(cf, pcs.pushContDecoder::onDecoded); // if all continuations are sent, set pushWithContinuation to null if (cf.endHeaders()) { completePushPromise(pcs.pushContFrame.getPromisedStream(), parent, @@ -1135,6 +1139,21 @@ private void sendConnectionPreface() throws IOException { subscriber.onNext(List.of(EMPTY_TRIGGER)); } + /** + * Called to get the initial stream after a connection upgrade. + * If the stream was cancelled, it might no longer be in the + * stream map. Therefore - we use the initialStream field + * instead, and reset it to null after returning it. + * @param the response type + * @return the initial stream created during the upgrade. + */ + @SuppressWarnings("unchecked") + Stream getInitialStream() { + var s = (Stream) initialStream; + initialStream = null; + return s; + } + /** * Returns an existing Stream with given id, or null if doesn't exist */ @@ -1452,76 +1471,6 @@ final String dbgString() { + connection.getConnectionFlow() + ")"; } - static class HeaderDecoder extends ValidatingHeadersConsumer { - - HttpHeadersBuilder headersBuilder; - - HeaderDecoder() { - this.headersBuilder = new HttpHeadersBuilder(); - } - - @Override - public void onDecoded(CharSequence name, CharSequence value) { - String n = name.toString(); - String v = value.toString(); - super.onDecoded(n, v); - headersBuilder.addHeader(n, v); - } - - HttpHeaders headers() { - return headersBuilder.build(); - } - } - - /* - * Checks RFC 7540 rules (relaxed) compliance regarding pseudo-headers. - */ - static class ValidatingHeadersConsumer implements DecodingCallback { - - private static final Set PSEUDO_HEADERS = - Set.of(":authority", ":method", ":path", ":scheme", ":status"); - - /** Used to check that if there are pseudo-headers, they go first */ - private boolean pseudoHeadersEnded; - - /** - * Called when END_HEADERS was received. This consumer may be invoked - * again after reset() is called, but for a whole new set of headers. - */ - void reset() { - pseudoHeadersEnded = false; - } - - @Override - public void onDecoded(CharSequence name, CharSequence value) - throws UncheckedIOException - { - String n = name.toString(); - if (n.startsWith(":")) { - if (pseudoHeadersEnded) { - throw newException("Unexpected pseudo-header '%s'", n); - } else if (!PSEUDO_HEADERS.contains(n)) { - throw newException("Unknown pseudo-header '%s'", n); - } - } else { - pseudoHeadersEnded = true; - if (!Utils.isValidName(n)) { - throw newException("Bad header name '%s'", n); - } - } - String v = value.toString(); - if (!Utils.isValidValue(v)) { - throw newException("Bad header value '%s'", v); - } - } - - private UncheckedIOException newException(String message, String header) - { - return new UncheckedIOException( - new IOException(String.format(message, header))); - } - } - static final class ConnectionWindowUpdateSender extends WindowUpdateSender { final int initialWindowSize; 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 921dfbebf48..87e8e00c68e 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 @@ -470,7 +470,7 @@ void otherFrame(Http2Frame frame) throws IOException { // The Hpack decoder decodes into one of these consumers of name,value pairs DecodingCallback rspHeadersConsumer() { - return rspHeadersConsumer; + return rspHeadersConsumer::onDecoded; } protected void handleResponse() throws IOException { @@ -1514,9 +1514,10 @@ final String dbgString() { return connection.dbgString() + "/Stream("+streamid+")"; } - private class HeadersConsumer extends Http2Connection.ValidatingHeadersConsumer { + private class HeadersConsumer extends ValidatingHeadersConsumer { - void reset() { + @Override + public void reset() { super.reset(); responseHeadersBuilder.clear(); debug.log("Response builder cleared, ready to receive new headers."); @@ -1526,15 +1527,28 @@ void reset() { public void onDecoded(CharSequence name, CharSequence value) throws UncheckedIOException { - String n = name.toString(); - String v = value.toString(); - super.onDecoded(n, v); - responseHeadersBuilder.addHeader(n, v); - if (Log.headers() && Log.trace()) { - Log.logTrace("RECEIVED HEADER (streamid={0}): {1}: {2}", - streamid, n, v); + try { + String n = name.toString(); + String v = value.toString(); + super.onDecoded(n, v); + responseHeadersBuilder.addHeader(n, v); + if (Log.headers() && Log.trace()) { + Log.logTrace("RECEIVED HEADER (streamid={0}): {1}: {2}", + streamid, n, v); + } + } catch (UncheckedIOException uio) { + // reset stream: From RFC 9113, section 8.1 + // Malformed requests or responses that are detected MUST be + // treated as a stream error (Section 5.4.2) of type + // PROTOCOL_ERROR. + onProtocolError(uio.getCause()); } } + + @Override + protected String formatMessage(String message, String header) { + return "malformed response: " + super.formatMessage(message, header); + } } private static final VarHandle STREAM_STATE; 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 new file mode 100644 index 00000000000..62d03844d2e --- /dev/null +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/HeaderDecoder.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.internal.net.http.common; + +import java.net.http.HttpHeaders; + +public class HeaderDecoder extends ValidatingHeadersConsumer { + + private final HttpHeadersBuilder headersBuilder; + + public HeaderDecoder() { + this.headersBuilder = new HttpHeadersBuilder(); + } + + @Override + public void onDecoded(CharSequence name, CharSequence value) { + String n = name.toString(); + String v = value.toString(); + super.onDecoded(n, v); + headersBuilder.addHeader(n, v); + } + + public HttpHeaders headers() { + return headersBuilder.build(); + } +} 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 b4e4010cbfb..8e9d7eb7507 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 @@ -371,16 +371,21 @@ public static URLPermission permissionForServer(URI uri, } + private static final boolean[] LOWER_CASE_CHARS = new boolean[128]; + // ABNF primitives defined in RFC 7230 private static final boolean[] tchar = new boolean[256]; private static final boolean[] fieldvchar = new boolean[256]; static { - char[] allowedTokenChars = - ("!#$%&'*+-.^_`|~0123456789" + - "abcdefghijklmnopqrstuvwxyz" + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); - for (char c : allowedTokenChars) { + char[] lcase = ("!#$%&'*+-.^_`|~0123456789" + + "abcdefghijklmnopqrstuvwxyz").toCharArray(); + for (char c : lcase) { + tchar[c] = true; + LOWER_CASE_CHARS[c] = true; + } + char[] ucase = ("ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray(); + for (char c : ucase) { tchar[c] = true; } for (char c = 0x21; c < 0xFF; c++) { @@ -389,6 +394,16 @@ public static URLPermission permissionForServer(URI uri, fieldvchar[0x7F] = false; // a little hole (DEL) in the range } + public static boolean isValidLowerCaseName(String token) { + for (int i = 0; i < token.length(); i++) { + char c = token.charAt(i); + if (c > 255 || !LOWER_CASE_CHARS[c]) { + return false; + } + } + return !token.isEmpty(); + } + /* * Validates a RFC 7230 field-name. */ diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/ValidatingHeadersConsumer.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/ValidatingHeadersConsumer.java new file mode 100644 index 00000000000..db873cdb05e --- /dev/null +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/ValidatingHeadersConsumer.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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.internal.net.http.common; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.Set; + +/* + * Checks RFC 9113 rules (relaxed) compliance regarding pseudo-headers. + */ +public class ValidatingHeadersConsumer { + + private static final Set PSEUDO_HEADERS = + Set.of(":authority", ":method", ":path", ":scheme", ":status"); + + /** Used to check that if there are pseudo-headers, they go first */ + private boolean pseudoHeadersEnded; + + /** + * Called when END_HEADERS was received. This consumer may be invoked + * again after reset() is called, but for a whole new set of headers. + */ + public void reset() { + pseudoHeadersEnded = false; + } + + /** + * Called when a header field (name, value) pair has been decoded + * @param name the decoded name + * @param value the decoded value + * @throws UncheckedIOException if the name or value are illegal + */ + public void onDecoded(CharSequence name, CharSequence value) + throws UncheckedIOException + { + String n = name.toString(); + if (n.startsWith(":")) { + if (pseudoHeadersEnded) { + throw newException("Unexpected pseudo-header '%s'", n); + } else if (!PSEUDO_HEADERS.contains(n)) { + throw newException("Unknown pseudo-header '%s'", n); + } + } else { + pseudoHeadersEnded = true; + // RFC-9113, section 8.2.1 for HTTP/2 and RFC-9114, section 4.2 state that + // header name MUST be lowercase (and allowed characters) + if (!Utils.isValidLowerCaseName(n)) { + throw newException("Bad header name '%s'", n); + } + } + String v = value.toString(); + if (!Utils.isValidValue(v)) { + throw newException("Bad header value '%s'", v); + } + } + + protected String formatMessage(String message, String header) { + return String.format(message, header); + } + + protected UncheckedIOException newException(String message, String header) + { + return new UncheckedIOException( + new IOException(formatMessage(message, header))); + } +} diff --git a/test/jdk/java/net/httpclient/http2/BadHeadersTest.java b/test/jdk/java/net/httpclient/http2/BadHeadersTest.java index 2e4765fdf1b..ce7adb25b58 100644 --- a/test/jdk/java/net/httpclient/http2/BadHeadersTest.java +++ b/test/jdk/java/net/httpclient/http2/BadHeadersTest.java @@ -23,6 +23,7 @@ /* * @test + * @bug 8303965 * @modules java.base/sun.net.www.http * java.net.http/jdk.internal.net.http.common * java.net.http/jdk.internal.net.http.frame @@ -201,20 +202,26 @@ void testAsync(String uri, // Assertions based on implementation specific detail messages. Keep in // sync with implementation. static void assertDetailMessage(Throwable throwable, int iterationIndex) { - assertTrue(throwable instanceof IOException, - "Expected IOException, got, " + throwable); - assertTrue(throwable.getMessage().contains("protocol error"), - "Expected \"protocol error\" in: " + throwable.getMessage()); - - if (iterationIndex == 0) { // unknown - assertTrue(throwable.getMessage().contains("Unknown pseudo-header"), - "Expected \"Unknown pseudo-header\" in: " + throwable.getMessage()); - } else if (iterationIndex == 4) { // unexpected - assertTrue(throwable.getMessage().contains(" Unexpected pseudo-header"), - "Expected \" Unexpected pseudo-header\" in: " + throwable.getMessage()); - } else { - assertTrue(throwable.getMessage().contains("Bad header"), - "Expected \"Bad header\" in: " + throwable.getMessage()); + try { + assertTrue(throwable instanceof IOException, + "Expected IOException, got, " + throwable); + assertTrue(throwable.getMessage().contains("malformed response"), + "Expected \"malformed response\" in: " + throwable.getMessage()); + + if (iterationIndex == 0) { // unknown + assertTrue(throwable.getMessage().contains("Unknown pseudo-header"), + "Expected \"Unknown pseudo-header\" in: " + throwable.getMessage()); + } else if (iterationIndex == 4) { // unexpected + assertTrue(throwable.getMessage().contains(" Unexpected pseudo-header"), + "Expected \" Unexpected pseudo-header\" in: " + throwable.getMessage()); + } else { + assertTrue(throwable.getMessage().contains("Bad header"), + "Expected \"Bad header\" in: " + throwable.getMessage()); + } + } catch (AssertionError e) { + System.out.println("Exception does not match expectation: " + throwable); + throwable.printStackTrace(System.out); + throw e; } } From 68c131d590ef20394d12f0a88a9bf4a1c0bea5ed Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Fri, 27 Sep 2024 21:03:17 -0700 Subject: [PATCH 06/16] 8284585: PushPromiseContinuation test fails intermittently in timeout Reviewed-by: mbalao Backport-of: 65da38d844760f7d17a143f8b4d5e25ea0144e27 --- .../internal/net/http/Http2Connection.java | 10 ++- .../http2/PushPromiseContinuation.java | 78 ++++++++++++++++--- .../server/Http2TestServerConnection.java | 5 ++ .../http2/server/OutgoingPushPromise.java | 18 ++++- 4 files changed, 94 insertions(+), 17 deletions(-) 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 6d03ca5ae98..b7bf18bf7b8 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 @@ -768,7 +768,7 @@ void processFrame(Http2Frame frame) throws IOException { } Stream stream = getStream(streamid); - if (stream == null) { + if (stream == null && pushContinuationState == null) { // Should never receive a frame with unknown stream id if (frame instanceof HeaderFrame) { @@ -808,7 +808,11 @@ void processFrame(Http2Frame frame) throws IOException { if (pushContinuationState != null) { if (frame instanceof ContinuationFrame) { try { - handlePushContinuation(stream, (ContinuationFrame)frame); + if (streamid == pushContinuationState.pushContFrame.streamid()) + handlePushContinuation(stream, (ContinuationFrame)frame); + else + protocolError(ErrorFrame.PROTOCOL_ERROR, "Received a Continuation Frame with an " + + "unexpected stream id"); } catch (UncheckedIOException e) { debug.log("Error handling Push Promise with Continuation: " + e.getMessage(), e); protocolError(ErrorFrame.PROTOCOL_ERROR, e.getMessage()); @@ -895,8 +899,6 @@ private void handlePushContinuation(Stream parent, ContinuationFrame cf) private void completePushPromise(int promisedStreamid, Stream parent, HttpHeaders headers) throws IOException { - // Perhaps the following checks could be moved to handlePushPromise() - // to reset the PushPromise stream earlier? HttpRequestImpl parentReq = parent.request; if (promisedStreamid != nextPushStream) { resetStream(promisedStreamid, ResetFrame.PROTOCOL_ERROR); diff --git a/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java b/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java index 49e76b7c274..bc8627a2bcc 100644 --- a/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java +++ b/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java @@ -61,12 +61,13 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.BiPredicate; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; +import static org.testng.Assert.*; public class PushPromiseContinuation { @@ -97,7 +98,7 @@ public void setup() throws Exception { // Need to have a custom exchange supplier to manage the server's push // promise with continuation flow - server.setExchangeSupplier(Http2LPPTestExchangeImpl::new); + server.setExchangeSupplier(Http2PushPromiseContinuationExchangeImpl::new); System.err.println("PushPromiseContinuation: Server listening on port " + server.getAddress().getPort()); server.start(); @@ -166,6 +167,31 @@ public void testThreeContinuations() { verify(resp); } + @Test + public void testSendHeadersOnPushPromiseStream() throws Exception { + // This test server sends a push promise that should be followed by a continuation but + // incorrectly sends on Response Headers while the client awaits the continuation. + Http2TestServer faultyServer = new Http2TestServer(false, 0); + faultyServer.addHandler(new ServerPushHandler(), "/"); + faultyServer.setExchangeSupplier(Http2PushPromiseHeadersExchangeImpl::new); + System.err.println("PushPromiseContinuation: FaultyServer listening on port " + faultyServer.getAddress().getPort()); + faultyServer.start(); + + int faultyPort = faultyServer.getAddress().getPort(); + URI faultyUri = new URI("http://localhost:" + faultyPort + "/"); + + HttpClient client = HttpClient.newHttpClient(); + // Server is making a request to an incorrect URI + HttpRequest hreq = HttpRequest.newBuilder(faultyUri).version(HttpClient.Version.HTTP_2).GET().build(); + CompletableFuture> cf = + 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()); + System.err.println("Client received the following expected exception: " + t.getCause()); + faultyServer.stop(); + } + private void verify(HttpResponse resp) { assertEquals(resp.statusCode(), 200); assertEquals(resp.body(), mainResponseBody); @@ -186,15 +212,46 @@ private void verify(HttpResponse resp) { } } - static class Http2LPPTestExchangeImpl extends Http2TestExchangeImpl { + 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) { + super(streamid, method, reqheaders, rspheadersBuilder, uri, is, sslSession, os, conn, pushAllowed); + } + + + @Override + public void serverPush(URI uri, HttpHeaders headers, InputStream content) { + HttpHeadersBuilder headersBuilder = new HttpHeadersBuilder(); + headersBuilder.setHeader(":method", "GET"); + headersBuilder.setHeader(":scheme", uri.getScheme()); + headersBuilder.setHeader(":authority", uri.getAuthority()); + headersBuilder.setHeader(":path", uri.getPath()); + for (Map.Entry> entry : headers.map().entrySet()) { + for (String value : entry.getValue()) + headersBuilder.addHeader(entry.getKey(), value); + } + HttpHeaders combinedHeaders = headersBuilder.build(); + OutgoingPushPromise pp = new OutgoingPushPromise(streamid, uri, combinedHeaders, content); + // Indicates to the client that a continuation should be expected + pp.setFlag(0x0); + try { + conn.outputQ.put(pp); + // writeLoop will spin up thread to read the InputStream + } catch (IOException ex) { + System.err.println("TestServer: pushPromise exception: " + ex); + } + } + } + + static class Http2PushPromiseContinuationExchangeImpl extends Http2TestExchangeImpl { HttpHeadersBuilder pushPromiseHeadersBuilder; List cfs; - Http2LPPTestExchangeImpl(int streamid, String method, HttpHeaders reqheaders, - HttpHeadersBuilder rspheadersBuilder, URI uri, InputStream is, - SSLSession sslSession, BodyOutputStream os, - Http2TestServerConnection conn, boolean pushAllowed) { + Http2PushPromiseContinuationExchangeImpl(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); } @@ -248,7 +305,8 @@ public void serverPush(URI uri, HttpHeaders headers, InputStream content) { HttpHeaders pushPromiseHeaders = pushPromiseHeadersBuilder.build(); testHeaders = testHeadersBuilder.build(); // Create the Push Promise Frame - OutgoingPushPromise pp = new OutgoingPushPromise(streamid, uri, pushPromiseHeaders, content); + OutgoingPushPromise pp = new OutgoingPushPromise(streamid, uri, pushPromiseHeaders, content, cfs); + // Indicates to the client that a continuation should be expected pp.setFlag(0x0); @@ -256,10 +314,6 @@ public void serverPush(URI uri, HttpHeaders headers, InputStream content) { // Schedule push promise and continuation for sending conn.outputQ.put(pp); System.err.println("Server: Scheduled a Push Promise to Send"); - for (ContinuationFrame cf : cfs) { - conn.outputQ.put(cf); - System.err.println("Server: Scheduled a Continuation to Send"); - } } catch (IOException ex) { System.err.println("Server: pushPromise exception: " + ex); } diff --git a/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java b/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java index 5cb5c2a0d0f..8c5310c8d24 100644 --- a/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java +++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java @@ -949,6 +949,11 @@ private void handlePush(OutgoingPushPromise op) throws IOException { nextPushStreamId += 2; pp.streamid(op.parentStream); writeFrame(pp); + // No need to check for END_HEADERS flag here to allow for tests to simulate bad server side + // behavior i.e Continuation Frames included with END_HEADERS flag set + for (Http2Frame cf : op.getContinuations()) + writeFrame(cf); + final InputStream ii = op.is; final BodyOutputStream oo = new BodyOutputStream( promisedStreamid, diff --git a/test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java b/test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java index 7b2124b73da..cd4fca0a817 100644 --- a/test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java +++ b/test/jdk/java/net/httpclient/http2/server/OutgoingPushPromise.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2022, 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,9 @@ import java.io.InputStream; import java.net.URI; import java.net.http.HttpHeaders; +import java.util.List; + +import jdk.internal.net.http.frame.ContinuationFrame; import jdk.internal.net.http.frame.Http2Frame; // will be converted to a PushPromiseFrame in the writeLoop @@ -33,16 +36,29 @@ class OutgoingPushPromise extends Http2Frame { final URI uri; final InputStream is; final int parentStream; // not the pushed streamid + private final List continuations; public OutgoingPushPromise(int parentStream, URI uri, HttpHeaders headers, InputStream is) { + this(parentStream, uri, headers, is, List.of()); + } + + public OutgoingPushPromise(int parentStream, + URI uri, + HttpHeaders headers, + InputStream is, + List continuations) { super(0,0); this.uri = uri; this.headers = headers; this.is = is; this.parentStream = parentStream; + this.continuations = List.copyOf(continuations); } + public List getContinuations() { + return continuations; + } } From 2d393d8bb49ada5096799a059651b83de95658eb Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Sat, 28 Sep 2024 17:03:16 -0700 Subject: [PATCH 07/16] 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 | 54 +++- .../internal/net/http/Http2ClientImpl.java | 79 +++-- .../internal/net/http/Http2Connection.java | 282 ++++++++++++++---- .../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 | 114 ++++++- .../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 +- .../classes/sun/net/httpserver/Request.java | 35 ++- .../sun/net/httpserver/ServerConfig.java | 18 +- .../net/httpclient/HttpServerAdapters.java | 12 +- .../http2/PushPromiseContinuation.java | 9 +- .../http2/server/HpackTestEncoder.java | 172 +++++++++++ .../http2/server/Http2TestExchange.java | 13 + .../http2/server/Http2TestExchangeImpl.java | 16 +- .../server/Http2TestServerConnection.java | 155 ++++++++-- 25 files changed, 1075 insertions(+), 173 deletions(-) create mode 100644 test/jdk/java/net/httpclient/http2/server/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 cef44d1422a..795d99d926b 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 @@ -238,6 +238,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 95b82cda283..664e7ec0d33 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); } @@ -466,10 +489,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 { @@ -483,7 +524,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 ':': @@ -517,6 +566,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; } @@ -538,6 +590,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 9aaee966828..8840d0c5e58 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 @@ -169,6 +169,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 @@ -282,6 +284,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"; @@ -724,7 +739,7 @@ private void writeRequests() throws IOException { } ps = (PrintStream) http.getOutputStream(); connected=true; - responses = new MessageHeader(); + responses = new MessageHeader(maxHeaderSize); setRequests=false; writeRequests(); } @@ -879,7 +894,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; @@ -2766,7 +2781,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. @@ -2955,7 +2970,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 6bc00e464b6..3f53c7aaab2 100644 --- a/src/java.base/share/conf/net.properties +++ b/src/java.base/share/conf/net.properties @@ -137,3 +137,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 b501c2fe0bc..fa811fe20b9 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 @@ -40,6 +40,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; @@ -68,6 +69,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; @@ -90,6 +93,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; @@ -281,7 +286,7 @@ private void checkCancelled() { public void h2Upgrade() { upgrading = true; - request.setH2Upgrade(client.client2()); + request.setH2Upgrade(this); } synchronized IOException getCancelCause() { @@ -379,6 +384,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 = @@ -455,12 +461,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); @@ -717,6 +731,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 ac5a59e1db0..a3e25741e68 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.ByteBuffer; import java.util.ArrayList; @@ -33,6 +34,7 @@ import java.util.Locale; import java.util.Map; import java.net.http.HttpHeaders; +import jdk.internal.net.http.common.Utils; import static java.lang.String.format; import static java.util.Objects.requireNonNull; import static jdk.internal.net.http.common.Utils.ACCEPT_ALL; @@ -49,6 +51,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, @@ -166,11 +174,16 @@ private boolean canContinueParsing(ByteBuffer buffer) { } } - 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 =(char)input.get()) != CR) { if (c == LF) break; sb.append(c); + if (++count > max) { + checkMaxHeaderSize(sb.length()); + } } if (c == CR) { state = State.STATUS_LINE_FOUND_CR; @@ -187,6 +200,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); @@ -199,7 +213,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 = (char)input.get(); @@ -209,6 +239,7 @@ private void maybeStartHeaders(ByteBuffer input) { state = State.STATUS_LINE_END_LF; } else { sb.append(c); + checkMaxHeaderSize(sb.length()); state = State.HEADER; } } @@ -226,9 +257,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 = (char)input.get(); if (c == CR) { @@ -242,10 +275,13 @@ private void readResumeHeader(ByteBuffer input) { if (c == HT) c = SP; sb.append(c); + if (++count > max) { + checkMaxHeaderSize(sb.length()); + } } } - private void addHeaderFromString(String headerString) { + private void addHeaderFromString(String headerString) throws ProtocolException { assert sb.length() == 0; int idx = headerString.indexOf(':'); if (idx == -1) @@ -254,12 +290,12 @@ private void addHeaderFromString(String headerString) { if (name.isEmpty()) return; String value = headerString.substring(idx + 1, headerString.length()).trim(); - + 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 : (char)input.get(); if (c == LF) { @@ -269,15 +305,17 @@ 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; } } - private void resumeOrSecondCR(ByteBuffer input) { + private void resumeOrSecondCR(ByteBuffer input) throws ProtocolException { assert state == State.HEADER_FOUND_CR_LF; char c = (char)input.get(); if (c == CR || c == LF) { @@ -298,6 +336,7 @@ private void resumeOrSecondCR(ByteBuffer input) { } 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) { @@ -308,6 +347,7 @@ private void resumeOrSecondCR(ByteBuffer input) { 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 9f74a70d318..0f2db7738fc 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, 2018, 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 @@ -38,7 +38,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CompletableFuture; -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; @@ -48,6 +47,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 @@ -94,14 +94,19 @@ class Http2ClientImpl { CompletableFuture getConnectionFor(HttpRequestImpl req, Exchange exchange) { String key = Http2Connection.keyFor(req); + boolean pushEnabled = exchange.pushEnabled(); synchronized (this) { Http2Connection connection = connections.get(key); if (connection != null) { try { - if (connection.closed || !connection.reserveStream(true)) { + if (connection.closed + || !connection.reserveStream(true, pushEnabled)) { if (debug.on()) - debug.log("removing found closed or closing connection: %s", connection); + debug.log("removing connection from pool since " + + "it couldn't be reserved for use%s: %s", + pushEnabled ? " with server push enabled" : + "", connection); deleteConnection(connection); } else { // fast path if connection already exists @@ -128,7 +133,7 @@ CompletableFuture getConnectionFor(HttpRequestImpl req, synchronized (Http2ClientImpl.this) { if (conn != null) { try { - conn.reserveStream(true); + conn.reserveStream(true, exchange.pushEnabled()); } catch (IOException e) { throw new UncheckedIOException(e); // shouldn't happen } @@ -161,10 +166,21 @@ boolean offerConnection(Http2Connection c) { synchronized(this) { 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); @@ -204,8 +220,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(); @@ -215,14 +231,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 @@ -243,7 +252,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( @@ -252,14 +272,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, @@ -272,6 +293,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 b7bf18bf7b8..2953b297561 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, 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 @@ -29,6 +29,7 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.net.InetSocketAddress; +import java.net.ProtocolException; import java.net.URI; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -44,6 +45,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.function.Function; import java.util.function.Supplier; import javax.net.ssl.SSLEngine; @@ -246,15 +249,54 @@ void markPrefaceSent() { } private final class PushContinuationState { - final HeaderDecoder pushContDecoder; + final PushPromiseDecoder pushContDecoder; final PushPromiseFrame pushContFrame; - PushContinuationState(HeaderDecoder pushContDecoder, PushPromiseFrame pushContFrame) { + PushContinuationState(PushPromiseDecoder pushContDecoder, PushPromiseFrame pushContFrame) { this.pushContDecoder = pushContDecoder; this.pushContFrame = pushContFrame; } } + 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; + } + } + } + } + volatile boolean closed; //------------------------------------- @@ -288,12 +330,24 @@ private final class PushContinuationState { 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 @@ -301,19 +355,21 @@ private final class PushContinuationState { 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); @@ -332,14 +388,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); @@ -372,7 +430,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 @@ -396,7 +455,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); @@ -411,13 +471,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()); @@ -440,15 +502,21 @@ final HttpClientImpl client() { // if false returned then a new Http2Connection is required // if true, the the stream may be assigned to this connection // for server push, if false returned, then the stream should be cancelled - synchronized boolean reserveStream(boolean clientInitiated) throws IOException { + synchronized boolean reserveStream(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.deleteConnection(this); return false; - } else if (!clientInitiated && (lastReservedServerStreamid + 2) >= MAX_SERVER_STREAM_ID) { + } else if (!clientInitiated && (lastReservedServerStreamid >= MAX_SERVER_STREAM_ID - 2)) { setFinalStream(); client2.deleteConnection(this); return false; @@ -473,6 +541,10 @@ synchronized boolean reserveStream(boolean clientInitiated) throws IOException { return true; } + synchronized boolean shouldClose() { + return finalStream() && streams.isEmpty(); + } + /** * Throws an IOException if h2 was not negotiated */ @@ -595,6 +667,10 @@ String key() { return this.key; } + public boolean serverPushEnabled() { + return clientSettings.getParameter(SettingsFrame.ENABLE_PUSH) == 1; + } + boolean offerConnection() { return client2.offerConnection(this); } @@ -693,7 +769,7 @@ final void asyncReceive(ByteBuffer buffer) { } Throwable getRecordedCause() { - return cause; + return cause.get(); } void shutdown(Throwable t) { @@ -703,6 +779,7 @@ void shutdown(Throwable t) { if (closed == true) return; closed = true; } + cause.compareAndSet(null, t); if (Log.errors()) { if (!(t instanceof EOFException) || isActive()) { Log.logError(t); @@ -710,9 +787,8 @@ void shutdown(Throwable t) { Log.logError("Shutting down connection: {0}", t.getMessage()); } } - Throwable initialCause = this.cause; - if (initialCause == null) this.cause = t; client2.deleteConnection(this); + subscriber.stop(cause.get()); List> c = new LinkedList<>(streams.values()); for (Stream s : c) { try { @@ -767,17 +843,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) { + String protocolError = checkMaxOrphanedHeadersExceeded((HeaderFrame)frame); + 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((HeaderFrame)frame, decoder); + } catch (IOException | UncheckedIOException e) { protocolError(ResetFrame.PROTOCOL_ERROR, e.getMessage()); return; } @@ -805,29 +903,42 @@ 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) { + ContinuationFrame cf = (ContinuationFrame)frame; + if (stream == null) { + String protocolError = checkMaxOrphanedHeadersExceeded(cf); + if (protocolError != null) { + protocolError(ResetFrame.PROTOCOL_ERROR, protocolError); + return; + } + } try { - if (streamid == pushContinuationState.pushContFrame.streamid()) - handlePushContinuation(stream, (ContinuationFrame)frame); - 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) { try { handlePushPromise(stream, (PushPromiseFrame)frame); - } catch (UncheckedIOException e) { + } catch (IOException | UncheckedIOException e) { protocolError(ErrorFrame.PROTOCOL_ERROR, e.getMessage()); return; } @@ -835,7 +946,7 @@ void processFrame(Http2Frame frame) throws IOException { // decode headers try { decodeHeaders((HeaderFrame)frame, stream.rspHeadersConsumer()); - } catch (UncheckedIOException e) { + } catch (IOException | UncheckedIOException e) { debug.log("Error decoding headers: " + e.getMessage(), e); protocolError(ErrorFrame.PROTOCOL_ERROR, e.getMessage()); return; @@ -848,6 +959,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 (closed) return; if (debug.on()) { @@ -872,38 +993,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 { @@ -1032,9 +1180,15 @@ 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); + 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) @@ -1174,7 +1328,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); } void putStream(Stream stream, int streamid) { @@ -1236,16 +1390,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); + } } } } @@ -1308,6 +1464,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 { @@ -1366,7 +1524,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 = @@ -1387,10 +1545,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(); @@ -1425,6 +1582,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); @@ -1433,19 +1591,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(); } @@ -1457,6 +1617,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(); + } + } } synchronized 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 d437c950e04..cb0c018b961 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 @@ -563,7 +563,9 @@ private void debugCompleted(String tag, long startNanos, HttpRequest req) { ce.initCause(throwable); throw ce; } 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 a7a34e9e165..4f9d3cf56f0 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 @@ -272,10 +272,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 364c695f0b2..b625f035f2f 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, 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 @@ -35,6 +35,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; @@ -123,16 +124,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 87e8e00c68e..f97632c12a1 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.nio.ByteBuffer; import java.util.ArrayList; @@ -42,6 +43,7 @@ import java.util.concurrent.Flow; import java.util.concurrent.Flow.Subscription; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiPredicate; import java.net.http.HttpClient; @@ -49,10 +51,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. * @@ -137,6 +142,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) @@ -470,28 +478,62 @@ 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() 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) + if (responseCode < 100 || responseCode > 199) { this.finalResponseCodeReceived = true; + } else { + protocolErrorMsg = checkInterimResponseCountExceeded(); + } + + if (protocolErrorMsg != null) { + if (debug.on()) { + debug.log(protocolErrorMsg); + } + 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()) { @@ -510,6 +552,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(); } @@ -1067,7 +1118,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() @@ -1252,7 +1303,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); @@ -1321,6 +1372,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. @@ -1330,16 +1382,17 @@ static class PushedStream extends Stream { 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() { @@ -1432,7 +1485,16 @@ protected void handleResponse() { .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; @@ -1514,7 +1576,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() { @@ -1527,6 +1591,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(); @@ -1549,6 +1616,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) { + ((Stream.PushedStream)Stream.this).parent.onProtocolError(cause); + } + // cancel the stream, continue processing + onProtocolError(cause); + reset(); + } + } } private static final VarHandle STREAM_STATE; 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 8e9d7eb7507..984e6214b3c 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 @@ -515,6 +515,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 370677b5e16..7f56e091fe1 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,23 +274,26 @@ 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); + resumeReady(input, action); break; case INDEXED: resumeIndexed(input, action); @@ -280,7 +315,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)) { @@ -301,6 +336,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) { @@ -327,6 +365,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+) | @@ -344,6 +388,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; @@ -386,7 +432,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 { @@ -397,6 +443,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)) { @@ -404,6 +452,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 { @@ -437,7 +487,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 { @@ -457,6 +507,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(); @@ -465,6 +517,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); @@ -498,7 +552,7 @@ private void resumeLiteralWithIndexing(ByteBuffer input, private void resumeLiteralNeverIndexed(ByteBuffer input, DecodingCallback action) throws IOException { - if (!completeReading(input)) { + if (!completeReading(input, action)) { return; } try { @@ -509,6 +563,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)) { @@ -516,6 +572,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 { @@ -553,7 +611,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)) { @@ -563,6 +621,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(); @@ -572,6 +632,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/jdk.httpserver/share/classes/sun/net/httpserver/Request.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/Request.java index 03e2a9ee8f5..0e136d80437 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, 2021, 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 @@ -42,8 +42,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 { @@ -76,6 +78,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) { @@ -88,20 +91,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; @@ -139,13 +149,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*/ @@ -179,6 +198,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; } @@ -206,6 +230,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 34a50fcab3f..de2916bf468 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 @@ -48,6 +48,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; @@ -61,6 +62,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; @@ -103,6 +107,14 @@ public Void run () { "sun.net.httpserver.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); @@ -211,6 +223,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/HttpServerAdapters.java b/test/jdk/java/net/httpclient/HttpServerAdapters.java index 6696d3e5744..2b604c08c7f 100644 --- a/test/jdk/java/net/httpclient/HttpServerAdapters.java +++ b/test/jdk/java/net/httpclient/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 @@ -208,6 +208,7 @@ public static abstract class HttpTestExchange { 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); @@ -270,7 +271,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 @@ -331,6 +335,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/http2/PushPromiseContinuation.java b/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java index bc8627a2bcc..7eb21656bef 100644 --- a/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java +++ b/test/jdk/java/net/httpclient/http2/PushPromiseContinuation.java @@ -51,6 +51,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; @@ -187,7 +188,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(); } @@ -214,7 +216,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/http2/server/HpackTestEncoder.java b/test/jdk/java/net/httpclient/http2/server/HpackTestEncoder.java new file mode 100644 index 00000000000..04f549d7194 --- /dev/null +++ b/test/jdk/java/net/httpclient/http2/server/HpackTestEncoder.java @@ -0,0 +1,172 @@ +/* + * 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. + */ + +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/http2/server/Http2TestExchange.java b/test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java index 30aad46b178..3f068440b46 100644 --- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java +++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchange.java @@ -27,9 +27,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 { @@ -51,6 +54,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(); @@ -63,6 +72,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/http2/server/Http2TestExchangeImpl.java b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java index 38e22d3bc47..f4c6bdbf648 100644 --- a/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java +++ b/test/jdk/java/net/httpclient/http2/server/Http2TestExchangeImpl.java @@ -24,6 +24,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 javax.net.ssl.SSLSession; import java.io.IOException; @@ -35,6 +36,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.function.BiPredicate; public class Http2TestExchangeImpl implements Http2TestExchange { @@ -128,8 +130,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 { this.responseLength = responseLength; if (responseLength !=0 && rCode != 204 && !isHeadRequest()) { long clen = responseLength > 0 ? responseLength : 0; @@ -140,7 +147,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); @@ -154,6 +161,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/http2/server/Http2TestServerConnection.java b/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java index 8c5310c8d24..7e45743934d 100644 --- a/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.java +++ b/test/jdk/java/net/httpclient/http2/server/Http2TestServerConnection.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 @@ -22,6 +22,8 @@ */ 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; @@ -76,10 +78,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; /** @@ -98,7 +103,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; @@ -391,7 +396,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. */ @@ -452,7 +459,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) { @@ -738,6 +745,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(); } @@ -848,26 +863,38 @@ static boolean isServerStreamId(int streamid) { return (streamid & 0x01) == 0x00; } + final ReentrantLock headersLock = new ReentrantLock(); + /** Encodes an group of headers, without any ordering guarantees. */ 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); @@ -880,18 +907,23 @@ List encodeHeadersOrdered(List> headers) { 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); @@ -920,8 +952,49 @@ void writeLoop() { } if (frame instanceof ResponseHeaders) { ResponseHeaders rh = (ResponseHeaders)frame; - HeadersFrame hf = new HeadersFrame(rh.streamid(), rh.getFlags(), encodeHeaders(rh.headers)); - writeFrame(hf); + 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().limit(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 @@ -1232,11 +1305,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 6ae4b326fa910fb6446ff77821389748e0021c8c Mon Sep 17 00:00:00 2001 From: Martin Balao Date: Wed, 14 Aug 2024 21:12:00 +0000 Subject: [PATCH 08/16] 8328544: Improve handling of vectorization Reviewed-by: roland, yan Backport-of: b5174c9159fbffdf335ee6835267ba0e674cf432 --- src/hotspot/share/opto/superword.cpp | 475 ++++++++++++++++++++++++++- src/hotspot/share/opto/superword.hpp | 91 ++++- 2 files changed, 554 insertions(+), 12 deletions(-) diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp index 60c2c8e6fab..6b538b10784 100644 --- a/src/hotspot/share/opto/superword.cpp +++ b/src/hotspot/share/opto/superword.cpp @@ -3561,6 +3561,10 @@ int SWPointer::Tracer::_depth = 0; SWPointer::SWPointer(MemNode* mem, SuperWord* slp, Node_Stack *nstack, bool analyze_only) : _mem(mem), _slp(slp), _base(NULL), _adr(NULL), _scale(0), _offset(0), _invar(NULL), _negate_invar(false), + _has_int_index_after_convI2L(false), + _int_index_after_convI2L_offset(0), + _int_index_after_convI2L_invar(NULL), + _int_index_after_convI2L_scale(0), _nstack(nstack), _analyze_only(analyze_only), _stack_idx(0) #ifndef PRODUCT @@ -3619,6 +3623,11 @@ SWPointer::SWPointer(MemNode* mem, SuperWord* slp, Node_Stack *nstack, bool anal NOT_PRODUCT(if(_slp->is_trace_alignment()) _tracer.restore_depth();) NOT_PRODUCT(_tracer.ctor_6(mem);) + if (!is_safe_to_use_as_simple_form(base, adr)) { + assert(!valid(), "does not have simple form"); + return; + } + _base = base; _adr = adr; assert(valid(), "Usable"); @@ -3629,6 +3638,10 @@ SWPointer::SWPointer(MemNode* mem, SuperWord* slp, Node_Stack *nstack, bool anal SWPointer::SWPointer(SWPointer* p) : _mem(p->_mem), _slp(p->_slp), _base(NULL), _adr(NULL), _scale(0), _offset(0), _invar(NULL), _negate_invar(false), + _has_int_index_after_convI2L(false), + _int_index_after_convI2L_offset(0), + _int_index_after_convI2L_invar(NULL), + _int_index_after_convI2L_scale(0), _nstack(p->_nstack), _analyze_only(p->_analyze_only), _stack_idx(p->_stack_idx) #ifndef PRODUCT @@ -3636,6 +3649,354 @@ SWPointer::SWPointer(SWPointer* p) : #endif {} +// We would like to make decisions about aliasing (i.e. removing memory edges) and adjacency +// (i.e. which loads/stores can be packed) based on the simple form: +// +// s_pointer = adr + offset + invar + scale * ConvI2L(iv) +// +// However, we parse the compound-long-int form: +// +// c_pointer = adr + long_offset + long_invar + long_scale * ConvI2L(int_index) +// int_index = int_offset + int_invar + int_scale * iv +// +// In general, the simple and the compound-long-int form do not always compute the same pointer +// at runtime. For example, the simple form would give a different result due to an overflow +// in the int_index. +// +// Example: +// For both forms, we have: +// iv = 0 +// scale = 1 +// +// We now account the offset and invar once to the long part and once to the int part: +// Pointer 1 (long offset and long invar): +// long_offset = min_int +// long_invar = min_int +// int_offset = 0 +// int_invar = 0 +// +// Pointer 2 (int offset and int invar): +// long_offset = 0 +// long_invar = 0 +// int_offset = min_int +// int_invar = min_int +// +// This gives us the following pointers: +// Compound-long-int form pointers: +// Form: +// c_pointer = adr + long_offset + long_invar + long_scale * ConvI2L(int_offset + int_invar + int_scale * iv) +// +// Pointers: +// c_pointer1 = adr + min_int + min_int + 1 * ConvI2L(0 + 0 + 1 * 0) +// = adr + min_int + min_int +// = adr - 2^32 +// +// c_pointer2 = adr + 0 + 0 + 1 * ConvI2L(min_int + min_int + 1 * 0) +// = adr + ConvI2L(min_int + min_int) +// = adr + 0 +// = adr +// +// Simple form pointers: +// Form: +// s_pointer = adr + offset + invar + scale * ConvI2L(iv) +// s_pointer = adr + (long_offset + int_offset) + (long_invar + int_invar) + (long_scale * int_scale) * ConvI2L(iv) +// +// Pointers: +// s_pointer1 = adr + (min_int + 0 ) + (min_int + 0 ) + 1 * 0 +// = adr + min_int + min_int +// = adr - 2^32 +// s_pointer2 = adr + (0 + min_int ) + (0 + min_int ) + 1 * 0 +// = adr + min_int + min_int +// = adr - 2^32 +// +// We see that the two addresses are actually 2^32 bytes apart (derived from the c_pointers), but their simple form look identical. +// +// Hence, we need to determine in which cases it is safe to make decisions based on the simple +// form, rather than the compound-long-int form. If we cannot prove that using the simple form +// is safe (i.e. equivalent to the compound-long-int form), then we do not get a valid SWPointer, +// and the associated memop cannot be vectorized. +bool SWPointer::is_safe_to_use_as_simple_form(Node* base, Node* adr) const { +#ifndef _LP64 + // On 32-bit platforms, there is never an explicit int_index with ConvI2L for the iv. Thus, the + // parsed pointer form is always the simple form, with int operations: + // + // pointer = adr + offset + invar + scale * iv + // + assert(!_has_int_index_after_convI2L, "32-bit never has an int_index with ConvI2L for the iv"); + return true; +#else + + // Array accesses that are not Unsafe always have a RangeCheck which ensures that there is no + // int_index overflow. This implies that the conversion to long can be done separately: + // + // ConvI2L(int_index) = ConvI2L(int_offset) + ConvI2L(int_invar) + ConvI2L(scale) * ConvI2L(iv) + // + // And hence, the simple form is guaranteed to be identical to the compound-long-int form at + // runtime and the SWPointer is safe/valid to be used. + const TypeAryPtr* ary_ptr_t = _mem->adr_type()->isa_aryptr(); + if (ary_ptr_t != NULL) { + if (!_mem->is_unsafe_access()) { + return true; + } + } + + // We did not find the int_index. Just to be safe, reject this SWPointer. + if (!_has_int_index_after_convI2L) { + return false; + } + + int int_offset = _int_index_after_convI2L_offset; + Node* int_invar = _int_index_after_convI2L_invar; + int int_scale = _int_index_after_convI2L_scale; + int long_scale = _scale / int_scale; + + // If "int_index = iv", then the simple form is identical to the compound-long-int form. + // + // int_index = int_offset + int_invar + int_scale * iv + // = 0 0 1 * iv + // = iv + if (int_offset == 0 && int_invar == NULL && int_scale == 1) { + return true; + } + + // Intuition: What happens if the int_index overflows? Let us look at two pointers on the "overflow edge": + // + // pointer1 = adr + ConvI2L(int_index1) + // pointer2 = adr + ConvI2L(int_index2) + // + // int_index1 = max_int + 0 = max_int -> very close to but before the overflow + // int_index2 = max_int + 1 = min_int -> just enough to get the overflow + // + // When looking at the difference of pointer1 and pointer2, we notice that it is very large + // (almost 2^32). Since arrays have at most 2^31 elements, chances are high that pointer2 is + // an actual out-of-bounds access at runtime. These would normally be prevented by range checks + // at runtime. However, if the access was done by using Unsafe, where range checks are omitted, + // then an out-of-bounds access constitutes undefined behavior. This means that we are allowed to + // do anything, including changing the behavior. + // + // If we can set the right conditions, we have a guarantee that an overflow is either impossible + // (no overflow or range checks preventing that) or undefined behavior. In both cases, we are + // safe to do a vectorization. + // + // Approach: We want to prove a lower bound for the distance between these two pointers, and an + // upper bound for the size of a memory object. We can derive such an upper bound for + // arrays. We know they have at most 2^31 elements. If we know the size of the elements + // in bytes, we have: + // + // array_element_size_in_bytes * 2^31 >= max_possible_array_size_in_bytes + // >= array_size_in_bytes (ARR) + // + // If some small difference "delta" leads to an int_index overflow, we know that the + // int_index1 before overflow must have been close to max_int, and the int_index2 after + // the overflow must be close to min_int: + // + // pointer1 = adr + long_offset + long_invar + long_scale * ConvI2L(int_index1) + // =approx adr + long_offset + long_invar + long_scale * max_int + // + // pointer2 = adr + long_offset + long_invar + long_scale * ConvI2L(int_index2) + // =approx adr + long_offset + long_invar + long_scale * min_int + // + // We realize that the pointer difference is very large: + // + // difference =approx long_scale * 2^32 + // + // Hence, if we set the right condition for long_scale and array_element_size_in_bytes, + // we can prove that an overflow is impossible (or would imply undefined behaviour). + // + // We must now take this intuition, and develop a rigorous proof. We start by stating the problem + // more precisely, with the help of some definitions and the Statement we are going to prove. + // + // Definition: + // Two SWPointers are "comparable" (i.e. SWPointer::comparable is true, set with SWPointer::cmp()), + // iff all of these conditions apply for the simple form: + // 1) Both SWPointers are valid. + // 2) The adr are identical, or both are array bases of different arrays. + // 3) They have identical scale. + // 4) They have identical invar. + // 5) The difference in offsets is limited: abs(offset1 - offset2) < 2^31. (DIFF) + // + // For the Vectorization Optimization, we pair-wise compare SWPointers and determine if they are: + // 1) "not comparable": + // We do not optimize them (assume they alias, not assume adjacency). + // + // Whenever we chose this option based on the simple form, it is also correct based on the + // compound-long-int form, since we make no optimizations based on it. + // + // 2) "comparable" with different array bases at runtime: + // We assume they do not alias (remove memory edges), but not assume adjacency. + // + // Whenever we have two different array bases for the simple form, we also have different + // array bases for the compound-long-form. Since SWPointers provably point to different + // memory objects, they can never alias. + // + // 3) "comparable" with the same base address: + // We compute the relative pointer difference, and based on the load/store size we can + // compute aliasing and adjacency. + // + // We must find a condition under which the pointer difference of the simple form is + // identical to the pointer difference of the compound-long-form. We do this with the + // Statement below, which we then proceed to prove. + // + // Statement: + // If two SWPointers satisfy these 3 conditions: + // 1) They are "comparable". + // 2) They have the same base address. + // 3) Their long_scale is a multiple of the array element size in bytes: + // + // abs(long_scale) % array_element_size_in_bytes = 0 (A) + // + // Then their pointer difference of the simple form is identical to the pointer difference + // of the compound-long-int form. + // + // More precisely: + // Such two SWPointers by definition have identical adr, invar, and scale. + // Their simple form is: + // + // s_pointer1 = adr + offset1 + invar + scale * ConvI2L(iv) (B1) + // s_pointer2 = adr + offset2 + invar + scale * ConvI2L(iv) (B2) + // + // Thus, the pointer difference of the simple forms collapses to the difference in offsets: + // + // s_difference = s_pointer1 - s_pointer2 = offset1 - offset2 (C) + // + // Their compound-long-int form for these SWPointer is: + // + // c_pointer1 = adr + long_offset1 + long_invar1 + long_scale1 * ConvI2L(int_index1) (D1) + // int_index1 = int_offset1 + int_invar1 + int_scale1 * iv (D2) + // + // c_pointer2 = adr + long_offset2 + long_invar2 + long_scale2 * ConvI2L(int_index2) (D3) + // int_index2 = int_offset2 + int_invar2 + int_scale2 * iv (D4) + // + // And these are the offset1, offset2, invar and scale from the simple form (B1) and (B2): + // + // offset1 = long_offset1 + long_scale1 * ConvI2L(int_offset1) (D5) + // offset2 = long_offset2 + long_scale2 * ConvI2L(int_offset2) (D6) + // + // invar = long_invar1 + long_scale1 * ConvI2L(int_invar1) + // = long_invar2 + long_scale2 * ConvI2L(int_invar2) (D7) + // + // scale = long_scale1 * ConvI2L(int_scale1) + // = long_scale2 * ConvI2L(int_scale2) (D8) + // + // The pointer difference of the compound-long-int form is defined as: + // + // c_difference = c_pointer1 - c_pointer2 + // + // Thus, the statement claims that for the two SWPointer we have: + // + // s_difference = c_difference (Statement) + // + // We prove the Statement with the help of a Lemma: + // + // Lemma: + // There is some integer x, such that: + // + // c_difference = s_difference + array_element_size_in_bytes * x * 2^32 (Lemma) + // + // From condition (DIFF), we can derive: + // + // abs(s_difference) < 2^31 (E) + // + // Assuming the Lemma, we prove the Statement: + // If "x = 0" (intuitively: the int_index does not overflow), then: + // c_difference = s_difference + // and hence the simple form computes the same pointer difference as the compound-long-int form. + // If "x != 0" (intuitively: the int_index overflows), then: + // abs(c_difference) >= abs(s_difference + array_element_size_in_bytes * x * 2^32) + // >= array_element_size_in_bytes * 2^32 - abs(s_difference) + // -- apply (E) -- + // > array_element_size_in_bytes * 2^32 - 2^31 + // >= array_element_size_in_bytes * 2^31 + // -- apply (ARR) -- + // >= max_possible_array_size_in_bytes + // >= array_size_in_bytes + // + // This shows that c_pointer1 and c_pointer2 have a distance that exceeds the maximum array size. + // Thus, at least one of the two pointers must be outside of the array bounds. But we can assume + // that out-of-bounds accesses do not happen. If they still do, it is undefined behavior. Hence, + // we are allowed to do anything. We can also "safely" use the simple form in this case even though + // it might not match the compound-long-int form at runtime. + // QED Statement. + // + // We must now prove the Lemma. + // + // ConvI2L always truncates by some power of 2^32, i.e. there is some integer y such that: + // + // ConvI2L(y1 + y2) = ConvI2L(y1) + ConvI2L(y2) + 2^32 * y (F) + // + // It follows, that there is an integer y1 such that: + // + // ConvI2L(int_index1) = ConvI2L(int_offset1 + int_invar1 + int_scale1 * iv) + // -- apply (F) -- + // = ConvI2L(int_offset1) + // + ConvI2L(int_invar1) + // + ConvI2L(int_scale1) * ConvI2L(iv) + // + y1 * 2^32 (G) + // + // Thus, we can write the compound-long-int form (D1) as: + // + // c_pointer1 = adr + long_offset1 + long_invar1 + long_scale1 * ConvI2L(int_index1) + // -- apply (G) -- + // = adr + // + long_offset1 + // + long_invar1 + // + long_scale1 * ConvI2L(int_offset1) + // + long_scale1 * ConvI2L(int_invar1) + // + long_scale1 * ConvI2L(int_scale1) * ConvI2L(iv) + // + long_scale1 * y1 * 2^32 (H) + // + // And we can write the simple form as: + // + // s_pointer1 = adr + offset1 + invar + scale * ConvI2L(iv) + // -- apply (D5, D7, D8) -- + // = adr + // + long_offset1 + // + long_scale1 * ConvI2L(int_offset1) + // + long_invar1 + // + long_scale1 * ConvI2L(int_invar1) + // + long_scale1 * ConvI2L(int_scale1) * ConvI2L(iv) (K) + // + // We now compute the pointer difference between the simple (K) and compound-long-int form (H). + // Most terms cancel out immediately: + // + // sc_difference1 = c_pointer1 - s_pointer1 = long_scale1 * y1 * 2^32 (L) + // + // Rearranging the equation (L), we get: + // + // c_pointer1 = s_pointer1 + long_scale1 * y1 * 2^32 (M) + // + // And since long_scale1 is a multiple of array_element_size_in_bytes, there is some integer + // x1, such that (M) implies: + // + // c_pointer1 = s_pointer1 + array_element_size_in_bytes * x1 * 2^32 (N) + // + // With an analogue equation for c_pointer2, we can now compute the pointer difference for + // the compound-long-int form: + // + // c_difference = c_pointer1 - c_pointer2 + // -- apply (N) -- + // = s_pointer1 + array_element_size_in_bytes * x1 * 2^32 + // -(s_pointer2 + array_element_size_in_bytes * x2 * 2^32) + // -- where "x = x1 - x2" -- + // = s_pointer1 - s_pointer2 + array_element_size_in_bytes * x * 2^32 + // -- apply (C) -- + // = s_difference + array_element_size_in_bytes * x * 2^32 + // QED Lemma. + if (ary_ptr_t != NULL) { + BasicType array_element_bt = ary_ptr_t->elem()->array_element_basic_type(); + if (is_java_primitive(array_element_bt)) { + int array_element_size_in_bytes = type2aelembytes(array_element_bt); + if (abs(long_scale) % array_element_size_in_bytes == 0) { + return true; + } + } + } + + // General case: we do not know if it is safe to use the simple form. + return false; +#endif +} + bool SWPointer::is_main_loop_member(Node* n) const { Node* n_c = phase()->get_ctrl(n); return lpt()->is_member(phase()->get_loop(n_c)); @@ -3736,12 +4097,39 @@ bool SWPointer::scaled_iv(Node* n) { NOT_PRODUCT(_tracer.scaled_iv_6(n, _scale);) return true; } - } else if (opc == Op_ConvI2L) { + } else if (opc == Op_ConvI2L && !has_iv()) { if (n->in(1)->Opcode() == Op_CastII && n->in(1)->as_CastII()->has_range_check()) { // Skip range check dependent CastII nodes n = n->in(1); } + + // So far we have not found the iv yet, and are about to enter a ConvI2L subgraph, + // which may be the int index (that might overflow) for the memory access, of the form: + // + // int_index = int_offset + int_invar + int_scale * iv + // + // If we simply continue parsing with the current SWPointer, then the int_offset and + // int_invar simply get added to the long offset and invar. But for the checks in + // SWPointer::is_safe_to_use_as_simple_form() we need to have explicit access to the + // int_index. Thus, we must parse it explicitly here. For this, we use a temporary + // SWPointer, to pattern match the int_index sub-expression of the address. + + NOT_PRODUCT(Tracer::Depth dddd;) + SWPointer tmp(this); + NOT_PRODUCT(_tracer.scaled_iv_8(n, &tmp);) + + if (tmp.scaled_iv_plus_offset(n->in(1)) && tmp.has_iv()) { + // We successfully matched an integer index, of the form: + // int_index = int_offset + int_invar + int_scale * iv + _has_int_index_after_convI2L = true; + _int_index_after_convI2L_offset = tmp._offset; + _int_index_after_convI2L_invar = tmp._invar; + _int_index_after_convI2L_scale = tmp._scale; + } + + // Now parse it again for the real SWPointer. This makes sure that the int_offset, int_invar, + // and int_scale are properly added to the final SWPointer's offset, invar, and scale. if (scaled_iv_plus_offset(n->in(1))) { NOT_PRODUCT(_tracer.scaled_iv_7(n);) return true; @@ -3757,10 +4145,27 @@ bool SWPointer::scaled_iv(Node* n) { if (tmp.scaled_iv_plus_offset(n->in(1))) { if (tmp._invar == NULL || _slp->do_vector_loop()) { - int mult = 1 << n->in(2)->get_int(); - _scale = tmp._scale * mult; - _offset += tmp._offset * mult; + int scale = (int)(n->in(2)->get_int()); + int mult = 1 << scale; + // Accumulate scale. + _scale = tmp._scale * ((jint)mult); + // Accumulate offset. + jint shifted_offset = 0; + if (!try_LShiftI_no_overflow(tmp._offset, scale, shifted_offset)) { + return false; // shift overflow. + } + if (!try_AddI_no_overflow(_offset, shifted_offset, _offset)) { + return false; // add overflow. + } + // Accumulate invar. _invar = tmp._invar; + + // Forward info about the int_index: + _has_int_index_after_convI2L = tmp._has_int_index_after_convI2L; + _int_index_after_convI2L_offset = tmp._int_index_after_convI2L_offset; + _int_index_after_convI2L_invar = tmp._int_index_after_convI2L_invar; + _int_index_after_convI2L_scale = tmp._int_index_after_convI2L_scale; + NOT_PRODUCT(_tracer.scaled_iv_9(n, _scale, _offset, mult);) return true; } @@ -3780,7 +4185,9 @@ bool SWPointer::offset_plus_k(Node* n, bool negate) { int opc = n->Opcode(); if (opc == Op_ConI) { - _offset += negate ? -(n->get_int()) : n->get_int(); + if (!try_AddSubI_no_overflow(_offset, n->get_int(), negate, _offset)) { + return false; // add/sub overflow. + } NOT_PRODUCT(_tracer.offset_plus_k_2(n, _offset);) return true; } else if (opc == Op_ConL) { @@ -3789,7 +4196,9 @@ bool SWPointer::offset_plus_k(Node* n, bool negate) { if (t->higher_equal(TypeLong::INT)) { jlong loff = n->get_long(); jint off = (jint)loff; - _offset += negate ? -off : loff; + if (!try_AddSubI_no_overflow(_offset, off, negate, _offset)) { + return false; // add/sub overflow. + } NOT_PRODUCT(_tracer.offset_plus_k_3(n, _offset);) return true; } @@ -3808,11 +4217,15 @@ bool SWPointer::offset_plus_k(Node* n, bool negate) { if (n->in(2)->is_Con() && invariant(n->in(1))) { _negate_invar = negate; _invar = n->in(1); - _offset += negate ? -(n->in(2)->get_int()) : n->in(2)->get_int(); + if (!try_AddSubI_no_overflow(_offset, n->in(2)->get_int(), negate, _offset)) { + return false; // add/sub overflow. + } NOT_PRODUCT(_tracer.offset_plus_k_6(n, _invar, _negate_invar, _offset);) return true; } else if (n->in(1)->is_Con() && invariant(n->in(2))) { - _offset += negate ? -(n->in(1)->get_int()) : n->in(1)->get_int(); + if (!try_AddSubI_no_overflow(_offset, n->in(1)->get_int(), negate, _offset)) { + return false; // add/sub overflow. + } _negate_invar = negate; _invar = n->in(2); NOT_PRODUCT(_tracer.offset_plus_k_7(n, _invar, _negate_invar, _offset);) @@ -3823,11 +4236,15 @@ bool SWPointer::offset_plus_k(Node* n, bool negate) { if (n->in(2)->is_Con() && invariant(n->in(1))) { _negate_invar = negate; _invar = n->in(1); - _offset += !negate ? -(n->in(2)->get_int()) : n->in(2)->get_int(); + if (!try_AddSubI_no_overflow(_offset, n->in(2)->get_int(), !negate, _offset)) { + return false; // add/sub overflow. + } NOT_PRODUCT(_tracer.offset_plus_k_8(n, _invar, _negate_invar, _offset);) return true; } else if (n->in(1)->is_Con() && invariant(n->in(2))) { - _offset += negate ? -(n->in(1)->get_int()) : n->in(1)->get_int(); + if (!try_AddSubI_no_overflow(_offset, n->in(1)->get_int(), negate, _offset)) { + return false; // add/sub overflow. + } _negate_invar = !negate; _invar = n->in(2); NOT_PRODUCT(_tracer.offset_plus_k_9(n, _invar, _negate_invar, _offset);) @@ -3859,6 +4276,44 @@ bool SWPointer::offset_plus_k(Node* n, bool negate) { return false; } +bool SWPointer::try_AddI_no_overflow(jint offset1, jint offset2, jint& result) { + jlong long_offset = java_add((jlong)(offset1), (jlong)(offset2)); + jint int_offset = java_add( offset1, offset2); + if (long_offset != int_offset) { + return false; + } + result = int_offset; + return true; +} + +bool SWPointer::try_SubI_no_overflow(jint offset1, jint offset2, jint& result) { + jlong long_offset = java_subtract((jlong)(offset1), (jlong)(offset2)); + jint int_offset = java_subtract( offset1, offset2); + if (long_offset != int_offset) { + return false; + } + result = int_offset; + return true; +} + +bool SWPointer::try_AddSubI_no_overflow(jint offset1, jint offset2, bool is_sub, jint& result) { + if (is_sub) { + return try_SubI_no_overflow(offset1, offset2, result); + } else { + return try_AddI_no_overflow(offset1, offset2, result); + } +} + +bool SWPointer::try_LShiftI_no_overflow(jint offset, int shift, jint& result) { + jlong long_offset = java_shift_left((jlong)(offset), (julong)((jlong)(shift))); + jint int_offset = java_shift_left( offset, (juint)((jint)(shift))); + if (long_offset != int_offset) { + return false; + } + result = int_offset; + return true; +} + //----------------------------print------------------------ void SWPointer::print() { #ifndef PRODUCT diff --git a/src/hotspot/share/opto/superword.hpp b/src/hotspot/share/opto/superword.hpp index 65ed7088766..82392e5ae57 100644 --- a/src/hotspot/share/opto/superword.hpp +++ b/src/hotspot/share/opto/superword.hpp @@ -568,17 +568,63 @@ class SuperWord : public ResourceObj { //------------------------------SWPointer--------------------------- // Information about an address for dependence checking and vector alignment +// +// We parse and represent pointers of the simple form: +// +// pointer = adr + offset + invar + scale * ConvI2L(iv) +// +// Where: +// +// adr: the base address of an array (base = adr) +// OR +// some address to off-heap memory (base = TOP) +// +// offset: a constant offset +// invar: a runtime variable, which is invariant during the loop +// scale: scaling factor +// iv: loop induction variable +// +// But more precisely, we parse the composite-long-int form: +// +// pointer = adr + long_offset + long_invar + long_scale * ConvI2L(int_offset + inv_invar + int_scale * iv) +// +// pointer = adr + long_offset + long_invar + long_scale * ConvI2L(int_index) +// int_index = int_offset + int_invar + int_scale * iv +// +// However, for aliasing and adjacency checks (e.g. SWPointer::cmp()) we always use the simple form to make +// decisions. Hence, we must make sure to only create a "valid" SWPointer if the optimisations based on the +// simple form produce the same result as the compound-long-int form would. Intuitively, this depends on +// if the int_index overflows, but the precise conditions are given in SWPointer::is_safe_to_use_as_simple_form(). +// +// ConvI2L(int_index) = ConvI2L(int_offset + int_invar + int_scale * iv) +// = Convi2L(int_offset) + ConvI2L(int_invar) + ConvI2L(int_scale) * ConvI2L(iv) +// +// scale = long_scale * ConvI2L(int_scale) +// offset = long_offset + long_scale * ConvI2L(int_offset) +// invar = long_invar + long_scale * ConvI2L(int_invar) +// +// pointer = adr + offset + invar + scale * ConvI2L(iv) +// class SWPointer { protected: MemNode* _mem; // My memory reference node SuperWord* _slp; // SuperWord class - Node* _base; // NULL if unsafe nonheap reference - Node* _adr; // address pointer + // Components of the simple form: + Node* _base; // Base address of an array OR NULL if some off-heap memory. + Node* _adr; // Same as _base if an array pointer OR some off-heap memory pointer. jint _scale; // multiplier for iv (in bytes), 0 if no loop iv jint _offset; // constant offset (in bytes) Node* _invar; // invariant offset (in bytes), NULL if none bool _negate_invar; // if true then use: (0 - _invar) + + // The int_index components of the compound-long-int form. Used to decide if it is safe to use the + // simple form rather than the compound-long-int form that was parsed. + bool _has_int_index_after_convI2L; + int _int_index_after_convI2L_offset; + Node* _int_index_after_convI2L_invar; + int _int_index_after_convI2L_scale; + Node_Stack* _nstack; // stack used to record a swpointer trace of variants bool _analyze_only; // Used in loop unrolling only for swpointer trace uint _stack_idx; // Used in loop unrolling only for swpointer trace @@ -597,6 +643,8 @@ class SWPointer { // Match: offset is (k [+/- invariant]) bool offset_plus_k(Node* n, bool negate = false); + bool is_safe_to_use_as_simple_form(Node* base, Node* adr) const; + public: enum CMP { Less = 1, @@ -625,12 +673,45 @@ class SWPointer { Node_Stack* node_stack() { return _nstack; } // Comparable? + // We compute if and how two SWPointers can alias at runtime, i.e. if the two addressed regions of memory can + // ever overlap. There are essentially 3 relevant return states: + // - NotComparable: Synonymous to "unknown aliasing". + // We have no information about how the two SWPointers can alias. They could overlap, refer + // to another location in the same memory object, or point to a completely different object. + // -> Memory edge required. Aliasing unlikely but possible. + // + // - Less / Greater: Synonymous to "never aliasing". + // The two SWPointers may point into the same memory object, but be non-aliasing (i.e. we + // know both address regions inside the same memory object, but these regions are non- + // overlapping), or the SWPointers point to entirely different objects. + // -> No memory edge required. Aliasing impossible. + // + // - Equal: Synonymous to "overlap, or point to different memory objects". + // The two SWPointers either overlap on the same memory object, or point to two different + // memory objects. + // -> Memory edge required. Aliasing likely. + // + // In a future refactoring, we can simplify to two states: + // - NeverAlias: instead of Less / Greater + // - MayAlias: instead of Equal / NotComparable + // + // Two SWPointer are "comparable" (Less / Greater / Equal), iff all of these conditions apply: + // 1) Both are valid, i.e. expressible in the compound-long-int or simple form. + // 2) The adr are identical, or both are array bases of different arrays. + // 3) They have identical scale. + // 4) They have identical invar. + // 5) The difference in offsets is limited: abs(offset0 - offset1) < 2^31. int cmp(SWPointer& q) { if (valid() && q.valid() && (_adr == q._adr || (_base == _adr && q._base == q._adr)) && _scale == q._scale && _invar == q._invar && _negate_invar == q._negate_invar) { + jlong difference = abs(java_subtract((jlong)_offset, (jlong)q._offset)); + jlong max_diff = (jlong)1 << 31; + if (difference >= max_diff) { + return NotComparable; + } bool overlap = q._offset < _offset + memory_size() && _offset < q._offset + q.memory_size(); return overlap ? Equal : (_offset < q._offset ? Less : Greater); @@ -717,6 +798,12 @@ class SWPointer { } _tracer;//TRacer; #endif + + static bool try_AddI_no_overflow(jint offset1, jint offset2, jint& result); + static bool try_SubI_no_overflow(jint offset1, jint offset2, jint& result); + static bool try_AddSubI_no_overflow(jint offset1, jint offset2, bool is_sub, jint& result); + static bool try_LShiftI_no_overflow(jint offset1, int offset2, jint& result); + }; #endif // SHARE_VM_OPTO_SUPERWORD_HPP From d73c162a858890d4f4b8f0881f18f79ab610352c Mon Sep 17 00:00:00 2001 From: Martin Balao Date: Sat, 14 Sep 2024 00:31:00 +0000 Subject: [PATCH 09/16] 8335713: Enhance vectorization analysis Reviewed-by: roland Backport-of: 3c05ad2290936ec9abc3f271cb6bf89e18c3eea7 --- src/hotspot/share/opto/superword.cpp | 84 ++++++++++++++++++++++++---- src/hotspot/share/opto/superword.hpp | 1 + 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp index 6b538b10784..5bec2150404 100644 --- a/src/hotspot/share/opto/superword.cpp +++ b/src/hotspot/share/opto/superword.cpp @@ -4046,11 +4046,41 @@ bool SWPointer::scaled_iv_plus_offset(Node* n) { } } else if (opc == Op_SubI) { if (offset_plus_k(n->in(2), true) && scaled_iv_plus_offset(n->in(1))) { + // (offset1 + invar1 + scale * iv) - (offset2) or + // (offset1 + scale * iv) - (offset2 + invar1) + // Subtraction handled via "negate" flag of "offset_plus_k". NOT_PRODUCT(_tracer.scaled_iv_plus_offset_6(n);) return true; } - if (offset_plus_k(n->in(1)) && scaled_iv_plus_offset(n->in(2))) { - _scale *= -1; + SWPointer tmp(this); + if (offset_plus_k(n->in(1)) && tmp.scaled_iv_plus_offset(n->in(2))) { + // (offset1 + invar1) - (offset2 + scale * iv) or + // (offset1) - (offset2 + invar1 + scale * iv) + // Subtraction handled explicitly below. + assert(_scale == 0, "shouldn't be set yet"); + // _scale = -tmp._scale + if (!try_MulI_no_overflow(-1, tmp._scale, _scale)) { + return false; // mul overflow. + } + // _offset -= tmp._offset + if (!try_SubI_no_overflow(_offset, tmp._offset, _offset)) { + return false; // sub overflow. + } + // _invar -= tmp._invar + if (tmp._invar != NULL) { + if (_invar != NULL) { + return false; + } + _invar = tmp._invar; + _negate_invar = !tmp._negate_invar; + } + + // SWPointer tmp does not have an integer part to be forwarded + // (tmp._has_int_index_after_convI2L is false) because n is a SubI, all + // nodes above must also be of integer type (ConvL2I is not handled + // to allow a long) and ConvI2L (the only node that can add an integer + // part) won't be present. + NOT_PRODUCT(_tracer.scaled_iv_plus_offset_7(n);) return true; } @@ -4093,7 +4123,9 @@ bool SWPointer::scaled_iv(Node* n) { } } else if (opc == Op_LShiftI) { if (n->in(1) == iv() && n->in(2)->is_Con()) { - _scale = 1 << n->in(2)->get_int(); + if (!try_LShiftI_no_overflow(1, n->in(2)->get_int(), _scale)) { + return false; // shift overflow. + } NOT_PRODUCT(_tracer.scaled_iv_6(n, _scale);) return true; } @@ -4122,15 +4154,28 @@ bool SWPointer::scaled_iv(Node* n) { if (tmp.scaled_iv_plus_offset(n->in(1)) && tmp.has_iv()) { // We successfully matched an integer index, of the form: // int_index = int_offset + int_invar + int_scale * iv + // Forward scale. + assert(_scale == 0 && tmp._scale != 0, "iv only found just now"); + _scale = tmp._scale; + // Accumulate offset. + if (!try_AddI_no_overflow(_offset, tmp._offset, _offset)) { + return false; // add overflow. + } + // Forward invariant if not already found. + if (tmp._invar != NULL) { + if (_invar != NULL) { + return false; + } + _invar = tmp._invar; + _negate_invar = tmp._negate_invar; + } + // Set info about the int_index: + assert(!_has_int_index_after_convI2L, "no previous int_index discovered"); _has_int_index_after_convI2L = true; _int_index_after_convI2L_offset = tmp._offset; _int_index_after_convI2L_invar = tmp._invar; _int_index_after_convI2L_scale = tmp._scale; - } - // Now parse it again for the real SWPointer. This makes sure that the int_offset, int_invar, - // and int_scale are properly added to the final SWPointer's offset, invar, and scale. - if (scaled_iv_plus_offset(n->in(1))) { NOT_PRODUCT(_tracer.scaled_iv_7(n);) return true; } @@ -4145,13 +4190,14 @@ bool SWPointer::scaled_iv(Node* n) { if (tmp.scaled_iv_plus_offset(n->in(1))) { if (tmp._invar == NULL || _slp->do_vector_loop()) { - int scale = (int)(n->in(2)->get_int()); - int mult = 1 << scale; + int shift = (int)(n->in(2)->get_int()); // Accumulate scale. - _scale = tmp._scale * ((jint)mult); + if (!try_LShiftI_no_overflow(tmp._scale, shift, _scale)) { + return false; // shift overflow. + } // Accumulate offset. jint shifted_offset = 0; - if (!try_LShiftI_no_overflow(tmp._offset, scale, shifted_offset)) { + if (!try_LShiftI_no_overflow(tmp._offset, shift, shifted_offset)) { return false; // shift overflow. } if (!try_AddI_no_overflow(_offset, shifted_offset, _offset)) { @@ -4161,12 +4207,13 @@ bool SWPointer::scaled_iv(Node* n) { _invar = tmp._invar; // Forward info about the int_index: + assert(!_has_int_index_after_convI2L, "no previous int_index discovered"); _has_int_index_after_convI2L = tmp._has_int_index_after_convI2L; _int_index_after_convI2L_offset = tmp._int_index_after_convI2L_offset; _int_index_after_convI2L_invar = tmp._int_index_after_convI2L_invar; _int_index_after_convI2L_scale = tmp._int_index_after_convI2L_scale; - NOT_PRODUCT(_tracer.scaled_iv_9(n, _scale, _offset, mult);) + NOT_PRODUCT(_tracer.scaled_iv_9(n, _scale, _offset, 1 << shift);) return true; } } @@ -4305,6 +4352,9 @@ bool SWPointer::try_AddSubI_no_overflow(jint offset1, jint offset2, bool is_sub, } bool SWPointer::try_LShiftI_no_overflow(jint offset, int shift, jint& result) { + if (shift < 0 || shift > 31) { + return false; + } jlong long_offset = java_shift_left((jlong)(offset), (julong)((jlong)(shift))); jint int_offset = java_shift_left( offset, (juint)((jint)(shift))); if (long_offset != int_offset) { @@ -4314,6 +4364,16 @@ bool SWPointer::try_LShiftI_no_overflow(jint offset, int shift, jint& result) { return true; } +bool SWPointer::try_MulI_no_overflow(jint offset1, jint offset2, jint& result) { + jlong long_offset = java_multiply((jlong)(offset1), (jlong)(offset2)); + jint int_offset = java_multiply( offset1, offset2); + if (long_offset != int_offset) { + return false; + } + result = int_offset; + return true; +} + //----------------------------print------------------------ void SWPointer::print() { #ifndef PRODUCT diff --git a/src/hotspot/share/opto/superword.hpp b/src/hotspot/share/opto/superword.hpp index 82392e5ae57..c98c4fbda00 100644 --- a/src/hotspot/share/opto/superword.hpp +++ b/src/hotspot/share/opto/superword.hpp @@ -803,6 +803,7 @@ class SWPointer { static bool try_SubI_no_overflow(jint offset1, jint offset2, jint& result); static bool try_AddSubI_no_overflow(jint offset1, jint offset2, bool is_sub, jint& result); static bool try_LShiftI_no_overflow(jint offset1, int offset2, jint& result); + static bool try_MulI_no_overflow(jint offset1, jint offset2, jint& result); }; From 51657d990a40aed8f197bad24160765dde0a1f82 Mon Sep 17 00:00:00 2001 From: Alexey Bakhtin Date: Fri, 10 May 2024 14:23:56 -0700 Subject: [PATCH 10/16] 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 | 2 -- .../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(+), 61 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 0c75b905044..593a5a42a5c 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, 2015, 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 @@ -168,7 +168,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 27c2741d911..a94e5777789 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, 2015, 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 49a9b5f6481..67a9e0ad4b2 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, 2015, 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 @@ -270,9 +270,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 05194ae1364..914e7cb2907 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, 2017, 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; @@ -210,15 +211,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 fef1ac2a385..6a19c6b967f 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 @@ -909,15 +909,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 { @@ -1062,12 +1058,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; } @@ -1416,8 +1406,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 387edd76691..86fdaefbb37 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 @@ -301,4 +301,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 dc51f8b51fc..a15e4e85b36 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 new String("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 d0ebcc3f36b..436ed7564cc 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 @@ -313,8 +313,6 @@ public static String getErrorMessage(int i) { public static final boolean DEBUG = java.security.AccessController.doPrivileged( new sun.security.action.GetBooleanAction("sun.security.krb5.debug")); - public static final sun.security.util.HexDumpEncoder hexDumper = - new sun.security.util.HexDumpEncoder(); static { errMsgList = new Hashtable (); 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 e8b048869c4..7b874ced493 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 @@ -121,11 +121,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 f266dba7d8f..1465d109219 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; /** @@ -760,15 +760,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()); } } } @@ -869,7 +865,7 @@ private void promptForPass(boolean getPasswdFromSharedState) } if (debug) { System.out.println - ("password is " + new String(password)); + ("Get password from shared state"); } return; } From 56ded02fd8bcc61399b3ca5091d6e597759fb30e Mon Sep 17 00:00:00 2001 From: Alexei Voitylov Date: Tue, 13 Aug 2024 10:30:49 +0200 Subject: [PATCH 11/16] 8331446: Improve deserialization support Reviewed-by: yan, mbalao, andrew Backport-of: 8e4a392832f83e16d521024505b52c96d0a993f2 --- .../classes/java/text/MessageFormat.java | 59 +++++++++-- .../MessageFormat/MaxArgumentIndexTest.java | 97 +++++++++++++++++++ .../MessageFormat/SerializationTest.java | 96 ++++++++++++++++++ 3 files changed, 242 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 e54033cb8ff..ce2471350b2 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; @@ -982,6 +983,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; @@ -1232,6 +1235,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 offsets. Can also be thought of @@ -1456,6 +1462,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; @@ -1602,24 +1613,52 @@ private static final void copyAndFixQuotes(String source, int start, int end, * @throws InvalidObjectException if the objects read from the stream is invalid. */ 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. + */ + 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 bd85b8729d9c7ffb31c57f328f35f4391b7d72fe Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Tue, 9 Oct 2018 11:44:00 +0100 Subject: [PATCH 12/16] 8211920: Close server socket and cleanups in test/jdk/javax/naming/module/RunBasic.java Reviewed-by: yan, andrew Backport-of: e61252dc278fd44345c5377811552ed6760ffcf0 --- test/jdk/javax/naming/module/RunBasic.java | 11 +- .../src/test/test/ConnectWithAuthzId.java | 129 ++++++----- .../module/src/test/test/ConnectWithFoo.java | 95 ++++---- .../module/src/test/test/ReadByUrl.java | 99 +++++---- .../module/src/test/test/StoreFruit.java | 168 +++++++------- .../module/src/test/test/StoreObject.java | 161 +++++++------- .../module/src/test/test/StorePerson.java | 208 +++++++++--------- .../module/src/test/test/StoreRemote.java | 136 ++++++------ 8 files changed, 538 insertions(+), 469 deletions(-) diff --git a/test/jdk/javax/naming/module/RunBasic.java b/test/jdk/javax/naming/module/RunBasic.java index 54b77e23883..0fd9d2b28aa 100644 --- a/test/jdk/javax/naming/module/RunBasic.java +++ b/test/jdk/javax/naming/module/RunBasic.java @@ -27,8 +27,10 @@ import jdk.test.lib.process.ProcessTools; import java.io.IOException; +import java.net.InetAddress; import java.nio.file.Files; import java.nio.file.Path; +import java.time.Duration; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -65,6 +67,8 @@ public class RunBasic { private static final List JAVA_CMDS; + static final String HOST_NAME = InetAddress.getLoopbackAddress().getHostName(); + static { String javaPath = JDKToolFinder.getJDKTool("java"); @@ -85,6 +89,8 @@ public static void main(String[] args) throws Throwable { prepareModule("test", "--module-source-path", Path.of(TEST_SRC, "src").toString()); + System.out.println("Hostname: [" + HOST_NAME + "]"); + // run tests runTest("java.desktop", "test.StoreObject"); runTest("person", "test.StorePerson"); @@ -98,9 +104,12 @@ public static void main(String[] args) throws Throwable { private static void prepareModule(String mod, String... opts) throws IOException { System.out.println("Preparing the '" + mod + "' module..."); + long start = System.nanoTime(); makeDir("mods", mod); CompilerUtils.compile(Path.of(TEST_SRC, "src", mod), Path.of("mods", (mod.equals("test") ? "" : mod)), opts); + Duration duration = Duration.ofNanos(System.nanoTime() - start); + System.out.println("completed: duration - " + duration ); } private static void makeDir(String first, String... more) @@ -111,7 +120,7 @@ private static void makeDir(String first, String... more) private static void runTest(String desc, String clsName) throws Throwable { System.out.println("Running with the '" + desc + "' module..."); runJava("-Dtest.src=" + TEST_SRC, "-p", "mods", "-m", "test/" + clsName, - "ldap://localhost/dc=ie,dc=oracle,dc=com"); + "ldap://" + HOST_NAME + "/dc=ie,dc=oracle,dc=com"); } private static void runJava(String... opts) throws Throwable { diff --git a/test/jdk/javax/naming/module/src/test/test/ConnectWithAuthzId.java b/test/jdk/javax/naming/module/src/test/test/ConnectWithAuthzId.java index 27e8e49b43d..5f707e44d69 100644 --- a/test/jdk/javax/naming/module/src/test/test/ConnectWithAuthzId.java +++ b/test/jdk/javax/naming/module/src/test/test/ConnectWithAuthzId.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, 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 @@ -29,6 +29,7 @@ package test; +import java.io.PrintStream; import java.net.*; import java.util.*; import javax.naming.*; @@ -40,12 +41,18 @@ public class ConnectWithAuthzId { + static { + final PrintStream out = new PrintStream(System.out, true); + final PrintStream err = new PrintStream(System.err, true); + + System.setOut(out); + System.setErr(err); + } + // LDAP capture file private static final String LDAP_CAPTURE_FILE = System.getProperty("test.src") + "/src/test/test/ConnectWithAuthzId.ldap"; - // LDAPServer socket - private static ServerSocket serverSocket; public static void main(String[] args) throws Exception { @@ -68,67 +75,69 @@ public static void main(String[] args) throws Exception { * Launch the LDAP server with the ConnectWithAuthzId.ldap capture file */ - serverSocket = new ServerSocket(0); - new Thread(new Runnable() { - @Override - public void run() { - try { - new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); - } catch (Exception e) { - System.out.println("ERROR: unable to launch LDAP server"); - e.printStackTrace(); - } + try (ServerSocket serverSocket = new ServerSocket()) { + serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + new Thread(new Runnable() { + @Override + public void run() { + try { + new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); + } catch (Exception e) { + System.out.println("ERROR: unable to launch LDAP server"); + e.printStackTrace(); + } + } + }).start(); + + /* + * Connect to the LDAP directory + */ + + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + URI ldapUri = new URI(args[0]); + if (ldapUri.getPort() == -1) { + ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), + serverSocket.getLocalPort(), ldapUri.getPath(), null, null); + } + env.put(Context.PROVIDER_URL, ldapUri.toString()); + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, "cn=admin,dc=ie,dc=oracle,dc=com"); + env.put(Context.SECURITY_CREDENTIALS, "changeit"); + env.put(LdapContext.CONTROL_FACTORIES, + "org.example.authz.AuthzIdResponseControlFactory"); + if (args[args.length - 1].equalsIgnoreCase("-trace")) { + env.put("com.sun.jndi.ldap.trace.ber", System.out); } - }).start(); - - /* - * Connect to the LDAP directory - */ - - Hashtable env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, - "com.sun.jndi.ldap.LdapCtxFactory"); - URI ldapUri = new URI(args[0]); - if (ldapUri.getPort() == -1) { - ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), - serverSocket.getLocalPort(), ldapUri.getPath(), null, null); - } - env.put(Context.PROVIDER_URL, ldapUri.toString()); - env.put(Context.SECURITY_AUTHENTICATION, "simple"); - env.put(Context.SECURITY_PRINCIPAL, "cn=admin,dc=ie,dc=oracle,dc=com"); - env.put(Context.SECURITY_CREDENTIALS, "changeit"); - env.put(LdapContext.CONTROL_FACTORIES, - "org.example.authz.AuthzIdResponseControlFactory"); - if (args[args.length - 1].equalsIgnoreCase("-trace")) { - env.put("com.sun.jndi.ldap.trace.ber", System.out); - } - System.out.println("ConnectWithAuthzId: connecting to " + ldapUri); - LdapContext ctx = null; - Control[] connectionControls = { new AuthzIdRequestControl(false) }; - - try { - ctx = new InitialLdapContext(env, connectionControls); - System.out.println("ConnectWithAuthzId: connected"); - // Retrieve the response controls - Control[] responseControls = ctx.getResponseControls(); - if (responseControls != null) { - for (Control responseControl : responseControls) { - System.out.println("ConnectWithAuthzId: received response" + - " control: " + responseControl.getID()); - if (responseControl instanceof AuthzIdResponseControl) { - AuthzIdResponseControl authzId = - (AuthzIdResponseControl)responseControl; - System.out.println("ConnectWithAuthzId: identity is " + - authzId.getIdentity()); + System.out.println("ConnectWithAuthzId: connecting to " + ldapUri); + LdapContext ctx = null; + Control[] connectionControls = { new AuthzIdRequestControl(false) }; + + try { + ctx = new InitialLdapContext(env, connectionControls); + System.out.println("ConnectWithAuthzId: connected"); + // Retrieve the response controls + Control[] responseControls = ctx.getResponseControls(); + if (responseControls != null) { + for (Control responseControl : responseControls) { + System.out.println("ConnectWithAuthzId: received response" + + " control: " + responseControl.getID()); + if (responseControl instanceof AuthzIdResponseControl) { + AuthzIdResponseControl authzId = + (AuthzIdResponseControl)responseControl; + System.out.println("ConnectWithAuthzId: identity is " + + authzId.getIdentity()); + } } } - } - } catch (NamingException e) { - System.err.println("ConnectWithAuthzId: error connecting " + e); - } finally { - if (ctx != null) { - ctx.close(); + } catch (NamingException e) { + System.err.println("ConnectWithAuthzId: error connecting " + e); + } finally { + if (ctx != null) { + ctx.close(); + } } } } diff --git a/test/jdk/javax/naming/module/src/test/test/ConnectWithFoo.java b/test/jdk/javax/naming/module/src/test/test/ConnectWithFoo.java index c14e4f4f31c..6df3b97cc61 100644 --- a/test/jdk/javax/naming/module/src/test/test/ConnectWithFoo.java +++ b/test/jdk/javax/naming/module/src/test/test/ConnectWithFoo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, 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 @@ -28,6 +28,7 @@ package test; +import java.io.PrintStream; import java.net.*; import java.util.*; import javax.naming.*; @@ -38,11 +39,17 @@ public class ConnectWithFoo { + static { + final PrintStream out = new PrintStream(System.out, true); + final PrintStream err = new PrintStream(System.err, true); + + System.setOut(out); + System.setErr(err); + } + // LDAP capture file private static final String LDAP_CAPTURE_FILE = System.getProperty("test.src") + "/src/test/test/ConnectWithFoo.ldap"; - // LDAPServer socket - private static ServerSocket serverSocket; public static void main(String[] args) throws Exception { @@ -65,48 +72,50 @@ public static void main(String[] args) throws Exception { * Launch the LDAP server with the ConnectWithFoo.ldap capture file */ - serverSocket = new ServerSocket(0); - new Thread(new Runnable() { - @Override - public void run() { - try { - new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); - } catch (Exception e) { - System.out.println("ERROR: unable to launch LDAP server"); - e.printStackTrace(); - } + try (ServerSocket serverSocket = new ServerSocket()) { + serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + new Thread(new Runnable() { + @Override + public void run() { + try { + new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); + } catch (Exception e) { + System.out.println("ERROR: unable to launch LDAP server"); + e.printStackTrace(); + } + } + }).start(); + + /* + * Connect to the LDAP directory + */ + + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + URI ldapUri = new URI(args[0]); + if (ldapUri.getPort() == -1) { + ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), + serverSocket.getLocalPort(), ldapUri.getPath(), null, null); + } + env.put(Context.PROVIDER_URL, ldapUri.toString()); + if (args[args.length - 1].equalsIgnoreCase("-trace")) { + env.put("com.sun.jndi.ldap.trace.ber", System.out); } - }).start(); - - /* - * Connect to the LDAP directory - */ - - Hashtable env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, - "com.sun.jndi.ldap.LdapCtxFactory"); - URI ldapUri = new URI(args[0]); - if (ldapUri.getPort() == -1) { - ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), - serverSocket.getLocalPort(), ldapUri.getPath(), null, null); - } - env.put(Context.PROVIDER_URL, ldapUri.toString()); - if (args[args.length - 1].equalsIgnoreCase("-trace")) { - env.put("com.sun.jndi.ldap.trace.ber", System.out); - } - System.out.println("ConnectWithFoo: connecting to " + ldapUri); - LdapContext ctx = null; - Control[] connectionControls = { new FooControl(false) }; - - try { - ctx = new InitialLdapContext(env, connectionControls); - System.out.println("ConnectWithFoo: connected"); - } catch (NamingException e) { - System.err.println("ConnectWithFoo: error connecting " + e); - } finally { - if (ctx != null) { - ctx.close(); + System.out.println("ConnectWithFoo: connecting to " + ldapUri); + LdapContext ctx = null; + Control[] connectionControls = { new FooControl(false) }; + + try { + ctx = new InitialLdapContext(env, connectionControls); + System.out.println("ConnectWithFoo: connected"); + } catch (NamingException e) { + System.err.println("ConnectWithFoo: error connecting " + e); + } finally { + if (ctx != null) { + ctx.close(); + } } } } diff --git a/test/jdk/javax/naming/module/src/test/test/ReadByUrl.java b/test/jdk/javax/naming/module/src/test/test/ReadByUrl.java index 4b68da455a3..cc2861b433b 100644 --- a/test/jdk/javax/naming/module/src/test/test/ReadByUrl.java +++ b/test/jdk/javax/naming/module/src/test/test/ReadByUrl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, 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 @@ -28,6 +28,7 @@ package test; +import java.io.PrintStream; import java.net.*; import java.util.*; import javax.naming.*; @@ -36,11 +37,17 @@ public class ReadByUrl { + static { + final PrintStream out = new PrintStream(System.out, true); + final PrintStream err = new PrintStream(System.err, true); + + System.setOut(out); + System.setErr(err); + } + // LDAP capture file private static final String LDAP_CAPTURE_FILE = System.getProperty("test.src") + "/src/test/test/ReadByUrl.ldap"; - // LDAPServer socket - private static ServerSocket serverSocket; public static void main(String[] args) throws Exception { @@ -63,50 +70,52 @@ public static void main(String[] args) throws Exception { * Launch the LDAP server with the ReadByUrl.ldap capture file */ - serverSocket = new ServerSocket(0); - new Thread(new Runnable() { - @Override - public void run() { - try { - new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); - } catch (Exception e) { - System.out.println("ERROR: unable to launch LDAP server"); - e.printStackTrace(); - } + try (ServerSocket serverSocket = new ServerSocket()) { + serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + new Thread(new Runnable() { + @Override + public void run() { + try { + new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); + } catch (Exception e) { + System.out.println("ERROR: unable to launch LDAP server"); + e.printStackTrace(); + } + } + }).start(); + + /* + * Connect to the LDAP directory + */ + + Hashtable env = new Hashtable<>(); + URI ldapUri = new URI(args[0]); + if (ldapUri.getPort() == -1) { + ldapUri = new URI("ldapv4", null, ldapUri.getHost(), + serverSocket.getLocalPort(), ldapUri.getPath(), null, null); + } + env.put(Context.PROVIDER_URL, ldapUri.toString()); + if (args[args.length - 1].equalsIgnoreCase("-trace")) { + env.put("com.sun.jndi.ldap.trace.ber", System.out); } - }).start(); - - /* - * Connect to the LDAP directory - */ - - Hashtable env = new Hashtable<>(); - URI ldapUri = new URI(args[0]); - if (ldapUri.getPort() == -1) { - ldapUri = new URI("ldapv4", null, ldapUri.getHost(), - serverSocket.getLocalPort(), ldapUri.getPath(), null, null); - } - env.put(Context.PROVIDER_URL, ldapUri.toString()); - if (args[args.length - 1].equalsIgnoreCase("-trace")) { - env.put("com.sun.jndi.ldap.trace.ber", System.out); - } - // URL context factory location for 'ldapv4://' - env.put(Context.URL_PKG_PREFIXES, "org.example"); - - System.out.println("ReadByUrl: connecting to " + ldapUri); - DirContext ctx = null; - - try { - ctx = new InitialDirContext(env); - System.out.println("ReadByUrl: connected"); - DirContext entry = (DirContext) ctx.lookup(ldapUri.toString()); - entry.close(); - } catch (NamingException e) { - System.err.println("ReadByUrl: error connecting " + e); - } finally { - if (ctx != null) { - ctx.close(); + // URL context factory location for 'ldapv4://' + env.put(Context.URL_PKG_PREFIXES, "org.example"); + + System.out.println("ReadByUrl: connecting to " + ldapUri); + DirContext ctx = null; + + try { + ctx = new InitialDirContext(env); + System.out.println("ReadByUrl: connected"); + DirContext entry = (DirContext) ctx.lookup(ldapUri.toString()); + entry.close(); + } catch (NamingException e) { + System.err.println("ReadByUrl: error connecting " + e); + } finally { + if (ctx != null) { + ctx.close(); + } } } } diff --git a/test/jdk/javax/naming/module/src/test/test/StoreFruit.java b/test/jdk/javax/naming/module/src/test/test/StoreFruit.java index 278102cdcdf..8121c6ac906 100644 --- a/test/jdk/javax/naming/module/src/test/test/StoreFruit.java +++ b/test/jdk/javax/naming/module/src/test/test/StoreFruit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, 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 @@ -29,6 +29,7 @@ package test; +import java.io.PrintStream; import java.net.*; import java.util.*; import javax.naming.*; @@ -38,18 +39,24 @@ public class StoreFruit { + static { + final PrintStream out = new PrintStream(System.out, true); + final PrintStream err = new PrintStream(System.err, true); + + System.setOut(out); + System.setErr(err); + } + + // LDAP capture file private static final String LDAP_CAPTURE_FILE = System.getProperty("test.src") + "/src/test/test/StoreFruit.ldap"; - // LDAPServer socket - private static ServerSocket serverSocket; public static void main(String[] args) throws Exception { /* * Process arguments */ - int argc = args.length; if ((argc < 1) || ((argc == 1) && (args[0].equalsIgnoreCase("-help")))) { @@ -58,97 +65,98 @@ public static void main(String[] args) throws Exception { System.err.println(" is the LDAP URL of the parent entry\n"); System.err.println("example:"); System.err.println(" java StoreFruit ldap://oasis/o=airius.com"); - return; + return; } /* * Launch the LDAP server with the StoreFruit.ldap capture file */ - - serverSocket = new ServerSocket(0); - new Thread(new Runnable() { - @Override - public void run() { - try { - new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); - } catch (Exception e) { - System.out.println("ERROR: unable to launch LDAP server"); - e.printStackTrace(); - } + try (ServerSocket serverSocket = new ServerSocket()) { + serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + new Thread(new Runnable() { + @Override + public void run() { + try { + new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); + } catch (Exception e) { + System.out.println("ERROR: unable to launch LDAP server"); + e.printStackTrace(); + } + } + }).start(); + + /* + * Store fruit objects in the LDAP directory + */ + + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + URI ldapUri = new URI(args[0]); + if (ldapUri.getPort() == -1) { + ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), + serverSocket.getLocalPort(), ldapUri.getPath(), null, null); + } + env.put(Context.PROVIDER_URL, ldapUri.toString()); + if (args[args.length - 1].equalsIgnoreCase("-trace")) { + env.put("com.sun.jndi.ldap.trace.ber", System.out); } - }).start(); - /* - * Store fruit objects in the LDAP directory - */ + System.out.println("StoreFruit: connecting to " + ldapUri); + DirContext ctx = new InitialDirContext(env); + Fruit fruit = null; + String dn = "cn=myfruit"; + String dn2 = "cn=myapple"; - Hashtable env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, - "com.sun.jndi.ldap.LdapCtxFactory"); - URI ldapUri = new URI(args[0]); - if (ldapUri.getPort() == -1) { - ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), - serverSocket.getLocalPort(), ldapUri.getPath(), null, null); - } - env.put(Context.PROVIDER_URL, ldapUri.toString()); - if (args[args.length - 1].equalsIgnoreCase("-trace")) { - env.put("com.sun.jndi.ldap.trace.ber", System.out); - } + try { + fruit = new Fruit("orange"); + ctx.bind(dn, fruit); + System.out.println("StoreFruit: created entry '" + dn + "'"); + } catch (NameAlreadyBoundException e) { + System.err.println("StoreFruit: entry '" + dn + + "' already exists"); + cleanup(ctx, (String)null); + return; + } - System.out.println("StoreFruit: connecting to " + ldapUri); - DirContext ctx = new InitialDirContext(env); - Fruit fruit = null; - String dn = "cn=myfruit"; - String dn2 = "cn=myapple"; - - try { - fruit = new Fruit("orange"); - ctx.bind(dn, fruit); - System.out.println("StoreFruit: created entry '" + dn + "'"); - } catch (NameAlreadyBoundException e) { - System.err.println("StoreFruit: entry '" + dn + - "' already exists"); - cleanup(ctx, (String)null); - return; - } + try { + ctx.bind(dn2, new Fruit("apple")); + System.out.println("StoreFruit: created entry '" + dn2 + "'"); + } catch (NameAlreadyBoundException e) { + System.err.println("StoreFruit: entry '" + dn2 + + "' already exists"); + cleanup(ctx, dn); + return; + } - try { - ctx.bind(dn2, new Fruit("apple")); - System.out.println("StoreFruit: created entry '" + dn2 + "'"); - } catch (NameAlreadyBoundException e) { - System.err.println("StoreFruit: entry '" + dn2 + - "' already exists"); - cleanup(ctx, dn); - return; - } + /* + * Retrieve fruit objects from the LDAP directory + */ - /* - * Retrieve fruit objects from the LDAP directory - */ + try { + Fruit fruit2 = (Fruit) ctx.lookup(dn); + System.out.println("StoreFruit: retrieved object: " + fruit2); + } catch (NamingException e) { + System.err.println("StoreFruit: error retrieving entry '" + + dn + "' " + e); + e.printStackTrace(); + cleanup(ctx, dn, dn2); + return; + } - try { - Fruit fruit2 = (Fruit) ctx.lookup(dn); - System.out.println("StoreFruit: retrieved object: " + fruit2); - } catch (NamingException e) { - System.err.println("StoreFruit: error retrieving entry '" + - dn + "' " + e); - e.printStackTrace(); - cleanup(ctx, dn, dn2); - return; - } + try { + Fruit fruit3 = (Fruit) ctx.lookup(dn2); + System.out.println("StoreFruit: retrieved object: " + fruit3); + } catch (NamingException e) { + System.err.println("StoreFruit: error retrieving entry '" + + dn2 + "' " + e); + e.printStackTrace(); + cleanup(ctx, dn, dn2); + return; + } - try { - Fruit fruit3 = (Fruit) ctx.lookup(dn2); - System.out.println("StoreFruit: retrieved object: " + fruit3); - } catch (NamingException e) { - System.err.println("StoreFruit: error retrieving entry '" + - dn2 + "' " + e); - e.printStackTrace(); cleanup(ctx, dn, dn2); - return; } - - cleanup(ctx, dn, dn2); } /* diff --git a/test/jdk/javax/naming/module/src/test/test/StoreObject.java b/test/jdk/javax/naming/module/src/test/test/StoreObject.java index 2834321962f..ce1625ee8d9 100644 --- a/test/jdk/javax/naming/module/src/test/test/StoreObject.java +++ b/test/jdk/javax/naming/module/src/test/test/StoreObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, 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,6 +30,7 @@ package test; import java.awt.event.ActionEvent; +import java.io.PrintStream; import java.net.*; import java.util.*; import javax.naming.*; @@ -37,11 +38,17 @@ public class StoreObject { + static { + final PrintStream out = new PrintStream(System.out, true); + final PrintStream err = new PrintStream(System.err, true); + + System.setOut(out); + System.setErr(err); + } + // LDAP capture file private static final String LDAP_CAPTURE_FILE = System.getProperty("test.src") + "/src/test/test/StoreObject.ldap"; - // LDAPServer socket - private static ServerSocket serverSocket; public static void main(String[] args) throws Exception { @@ -64,89 +71,91 @@ public static void main(String[] args) throws Exception { * Launch the LDAP server with the StoreObject.ldap capture file */ - serverSocket = new ServerSocket(0); - new Thread(new Runnable() { - @Override - public void run() { - try { - new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); - } catch (Exception e) { - System.out.println("ERROR: unable to launch LDAP server"); - e.printStackTrace(); - } + try (ServerSocket serverSocket = new ServerSocket()) { + serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + new Thread(new Runnable() { + @Override + public void run() { + try { + new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); + } catch (Exception e) { + System.out.println("ERROR: unable to launch LDAP server"); + e.printStackTrace(); + } + } + }).start(); + + /* + * Store objects in the LDAP directory + */ + + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + URI ldapUri = new URI(args[0]); + if (ldapUri.getPort() == -1) { + ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), + serverSocket.getLocalPort(), ldapUri.getPath(), null, null); + } + env.put(Context.PROVIDER_URL, ldapUri.toString()); + if (args[args.length - 1].equalsIgnoreCase("-trace")) { + env.put("com.sun.jndi.ldap.trace.ber", System.out); } - }).start(); - /* - * Store objects in the LDAP directory - */ + System.out.println("StoreObject: connecting to " + ldapUri); + DirContext ctx = new InitialDirContext(env); + String dn = "cn=myevent"; + String dn2 = "cn=myevent2"; - Hashtable env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, - "com.sun.jndi.ldap.LdapCtxFactory"); - URI ldapUri = new URI(args[0]); - if (ldapUri.getPort() == -1) { - ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), - serverSocket.getLocalPort(), ldapUri.getPath(), null, null); - } - env.put(Context.PROVIDER_URL, ldapUri.toString()); - if (args[args.length - 1].equalsIgnoreCase("-trace")) { - env.put("com.sun.jndi.ldap.trace.ber", System.out); - } + try { + ctx.bind(dn, new ActionEvent("", 1, "Hello1")); + System.out.println("StoreObject: created entry '" + dn + "'"); + } catch (NameAlreadyBoundException e) { + System.err.println("StoreObject: entry '" + dn + + "' already exists"); + cleanup(ctx, (String)null); + return; + } - System.out.println("StoreObject: connecting to " + ldapUri); - DirContext ctx = new InitialDirContext(env); - String dn = "cn=myevent"; - String dn2 = "cn=myevent2"; - - try { - ctx.bind(dn, new ActionEvent("", 1, "Hello1")); - System.out.println("StoreObject: created entry '" + dn + "'"); - } catch (NameAlreadyBoundException e) { - System.err.println("StoreObject: entry '" + dn + - "' already exists"); - cleanup(ctx, (String)null); - return; - } + try { + ctx.bind(dn2, new ActionEvent("", 2, "Hello2")); + System.out.println("StoreObject: created entry '" + dn2 + "'"); + } catch (NameAlreadyBoundException e) { + System.err.println("StoreObject: entry '" + dn2 + + "' already exists"); + cleanup(ctx, dn); + return; + } - try { - ctx.bind(dn2, new ActionEvent("", 2, "Hello2")); - System.out.println("StoreObject: created entry '" + dn2 + "'"); - } catch (NameAlreadyBoundException e) { - System.err.println("StoreObject: entry '" + dn2 + - "' already exists"); - cleanup(ctx, dn); - return; - } + /* + * Retrieve objects from the LDAP directory + */ - /* - * Retrieve objects from the LDAP directory - */ + try { + ActionEvent b = (ActionEvent) ctx.lookup(dn); + System.out.println("StoreObject: retrieved object: " + b); + } catch (NamingException e) { + System.err.println("StoreObject: error retrieving entry '" + + dn + "' " + e); + e.printStackTrace(); + cleanup(ctx, dn, dn2); + return; + } - try { - ActionEvent b = (ActionEvent) ctx.lookup(dn); - System.out.println("StoreObject: retrieved object: " + b); - } catch (NamingException e) { - System.err.println("StoreObject: error retrieving entry '" + - dn + "' " + e); - e.printStackTrace(); - cleanup(ctx, dn, dn2); - return; - } + try { + ActionEvent t = (ActionEvent) ctx.lookup(dn2); + System.out.println("StoreObject: retrieved object: " + t); + } catch (NamingException e) { + System.err.println("StoreObject: error retrieving entry '" + + dn2 + "' " + e); + e.printStackTrace(); + cleanup(ctx, dn, dn2); + return; + } - try { - ActionEvent t = (ActionEvent) ctx.lookup(dn2); - System.out.println("StoreObject: retrieved object: " + t); - } catch (NamingException e) { - System.err.println("StoreObject: error retrieving entry '" + - dn2 + "' " + e); - e.printStackTrace(); cleanup(ctx, dn, dn2); - return; + ctx.close(); } - - cleanup(ctx, dn, dn2); - ctx.close(); } /* diff --git a/test/jdk/javax/naming/module/src/test/test/StorePerson.java b/test/jdk/javax/naming/module/src/test/test/StorePerson.java index 4b6e174625f..6a5baebe5a9 100644 --- a/test/jdk/javax/naming/module/src/test/test/StorePerson.java +++ b/test/jdk/javax/naming/module/src/test/test/StorePerson.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, 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 @@ package test; +import java.io.PrintStream; import java.net.*; import java.util.*; import javax.naming.*; @@ -40,11 +41,17 @@ public class StorePerson { + static { + final PrintStream out = new PrintStream(System.out, true); + final PrintStream err = new PrintStream(System.err, true); + + System.setOut(out); + System.setErr(err); + } + // LDAP capture file private static final String LDAP_CAPTURE_FILE = System.getProperty("test.src") + "/src/test/test/StorePerson.ldap"; - // LDAPServer socket - private static ServerSocket serverSocket; public static void main(String[] args) throws Exception { @@ -67,115 +74,116 @@ public static void main(String[] args) throws Exception { * Launch the LDAP server with the StorePerson.ldap capture file */ - serverSocket = new ServerSocket(0); - new Thread(new Runnable() { - @Override - public void run() { - try { - new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); - } catch (Exception e) { - System.out.println("ERROR: unable to launch LDAP server"); - e.printStackTrace(); - } + try (ServerSocket serverSocket = new ServerSocket()) { + serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + new Thread(new Runnable() { + @Override + public void run() { + try { + new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); + } catch (Exception e) { + System.out.println("ERROR: unable to launch LDAP server"); + e.printStackTrace(); + } + } + }).start(); + + /* + * Store Person objects in the LDAP directory + */ + + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + URI ldapUri = new URI(args[0]); + if (ldapUri.getPort() == -1) { + ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), + serverSocket.getLocalPort(), ldapUri.getPath(), null, null); + } + env.put(Context.PROVIDER_URL, ldapUri.toString()); + if (args[args.length - 1].equalsIgnoreCase("-trace")) { + env.put("com.sun.jndi.ldap.trace.ber", System.out); } - }).start(); - /* - * Store Person objects in the LDAP directory - */ + // Specify the factory classname explicitly + env.put(Context.STATE_FACTORIES, "org.example.person.PersonFactory"); + env.put(Context.OBJECT_FACTORIES, "org.example.person.PersonFactory"); - Hashtable env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, - "com.sun.jndi.ldap.LdapCtxFactory"); - URI ldapUri = new URI(args[0]); - if (ldapUri.getPort() == -1) { - ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), - serverSocket.getLocalPort(), ldapUri.getPath(), null, null); - } - env.put(Context.PROVIDER_URL, ldapUri.toString()); - if (args[args.length - 1].equalsIgnoreCase("-trace")) { - env.put("com.sun.jndi.ldap.trace.ber", System.out); - } + System.out.println("StorePerson: connecting to " + ldapUri); + DirContext ctx = new InitialDirContext(env); + Person person = null; + String name = "John Smith"; + String dn = "cn=" + name; - // Specify the factory classname explicitly - env.put(Context.STATE_FACTORIES, "org.example.person.PersonFactory"); - env.put(Context.OBJECT_FACTORIES, "org.example.person.PersonFactory"); - - System.out.println("StorePerson: connecting to " + ldapUri); - DirContext ctx = new InitialDirContext(env); - Person person = null; - String name = "John Smith"; - String dn = "cn=" + name; - - try { - person = new Person(name, "Smith"); - person.setMailAddress("jsmith@smith.com"); - ctx.bind(dn, person); - System.out.println("StorePerson: created entry '" + dn + "'"); - } catch (NameAlreadyBoundException e) { - System.err.println("StorePerson: entry '" + dn + - "' already exists"); - cleanup(ctx, (String)null); - return; - } + try { + person = new Person(name, "Smith"); + person.setMailAddress("jsmith@smith.com"); + ctx.bind(dn, person); + System.out.println("StorePerson: created entry '" + dn + "'"); + } catch (NameAlreadyBoundException e) { + System.err.println("StorePerson: entry '" + dn + + "' already exists"); + cleanup(ctx, (String)null); + return; + } - name = "Jill Smyth"; - String dn2 = "cn=" + name; - Person person2 = new Person(name, "Smyth"); - person2.setMailAddress("jsmyth@smith.com"); - - try { - ctx.bind(dn2, person2); - System.out.println("StorePerson: created entry '" + dn2 + "'"); - } catch (NameAlreadyBoundException e) { - System.err.println("StorePerson: entry '" + dn2 + - "' already exists"); - cleanup(ctx, dn); - return; - } + name = "Jill Smyth"; + String dn2 = "cn=" + name; + Person person2 = new Person(name, "Smyth"); + person2.setMailAddress("jsmyth@smith.com"); - /* - * Retrieve Person objects from the LDAP directory - */ + try { + ctx.bind(dn2, person2); + System.out.println("StorePerson: created entry '" + dn2 + "'"); + } catch (NameAlreadyBoundException e) { + System.err.println("StorePerson: entry '" + dn2 + + "' already exists"); + cleanup(ctx, dn); + return; + } + + /* + * Retrieve Person objects from the LDAP directory + */ - try { - Person person3 = (Person) ctx.lookup(dn); - System.out.println("StorePerson: retrieved object: " + person3); - if (person.getAttributes().equals(person3.getAttributes())) { - System.out.println( - "StorePerson: retrieved person matches original"); - } else { - System.out.println( - "StorePerson: retrieved person does NOT match original"); + try { + Person person3 = (Person) ctx.lookup(dn); + System.out.println("StorePerson: retrieved object: " + person3); + if (person.getAttributes().equals(person3.getAttributes())) { + System.out.println( + "StorePerson: retrieved person matches original"); + } else { + System.out.println( + "StorePerson: retrieved person does NOT match original"); + } + } catch (NamingException e) { + System.err.println("StorePerson: error retrieving entry '" + + dn + "' " + e); + e.printStackTrace(); + cleanup(ctx, dn, dn2); + return; } - } catch (NamingException e) { - System.err.println("StorePerson: error retrieving entry '" + - dn + "' " + e); - e.printStackTrace(); - cleanup(ctx, dn, dn2); - return; - } - try { - Person person4 = (Person) ctx.lookup(dn2); - System.out.println("StorePerson: retrieved object: " + person4); - if (person2.getAttributes().equals(person4.getAttributes())) { - System.out.println( - "StorePerson: retrieved person matches original"); - } else { - System.out.println( - "StorePerson: retrieved person does NOT match original"); + try { + Person person4 = (Person) ctx.lookup(dn2); + System.out.println("StorePerson: retrieved object: " + person4); + if (person2.getAttributes().equals(person4.getAttributes())) { + System.out.println( + "StorePerson: retrieved person matches original"); + } else { + System.out.println( + "StorePerson: retrieved person does NOT match original"); + } + } catch (NamingException e) { + System.err.println("StorePerson: error retrieving entry '" + + dn2 + "' " + e); + e.printStackTrace(); + cleanup(ctx, dn, dn2); + return; } - } catch (NamingException e) { - System.err.println("StorePerson: error retrieving entry '" + - dn2 + "' " + e); - e.printStackTrace(); + cleanup(ctx, dn, dn2); - return; } - - cleanup(ctx, dn, dn2); - return; } /* diff --git a/test/jdk/javax/naming/module/src/test/test/StoreRemote.java b/test/jdk/javax/naming/module/src/test/test/StoreRemote.java index 95dea1bd327..a7c14138aae 100644 --- a/test/jdk/javax/naming/module/src/test/test/StoreRemote.java +++ b/test/jdk/javax/naming/module/src/test/test/StoreRemote.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2018, 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,11 +40,17 @@ public class StoreRemote { + static { + final PrintStream out = new PrintStream(System.out, true); + final PrintStream err = new PrintStream(System.err, true); + + System.setOut(out); + System.setErr(err); + } + // LDAP capture file private static final String LDAP_CAPTURE_FILE = System.getProperty("test.src") + "/src/test/test/StoreRemote.ldap"; - // LDAPServer socket - private static ServerSocket serverSocket; public static void main(String[] args) throws Exception { @@ -67,77 +73,79 @@ public static void main(String[] args) throws Exception { * Launch the LDAP server with the StoreRemote.ldap capture file */ - serverSocket = new ServerSocket(0); - new Thread(new Runnable() { - @Override - public void run() { - try { - new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); - } catch (Exception e) { - System.out.println("ERROR: unable to launch LDAP server"); - e.printStackTrace(); - } + try (ServerSocket serverSocket = new ServerSocket()){ + serverSocket.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); + new Thread(new Runnable() { + @Override + public void run() { + try { + new LDAPServer(serverSocket, LDAP_CAPTURE_FILE); + } catch (Exception e) { + System.out.println("ERROR: unable to launch LDAP server"); + e.printStackTrace(); + } + } + }).start(); + + /* + * Store a Remote object in the LDAP directory + */ + + Hashtable env = new Hashtable<>(); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + URI ldapUri = new URI(args[0]); + if (ldapUri.getPort() == -1) { + ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), + serverSocket.getLocalPort(), ldapUri.getPath(), null, null); + } + env.put(Context.PROVIDER_URL, ldapUri.toString()); + if (args[args.length - 1].equalsIgnoreCase("-trace")) { + env.put("com.sun.jndi.ldap.trace.ber", System.out); } - }).start(); - - /* - * Store a Remote object in the LDAP directory - */ - - Hashtable env = new Hashtable<>(); - env.put(Context.INITIAL_CONTEXT_FACTORY, - "com.sun.jndi.ldap.LdapCtxFactory"); - URI ldapUri = new URI(args[0]); - if (ldapUri.getPort() == -1) { - ldapUri = new URI(ldapUri.getScheme(), null, ldapUri.getHost(), - serverSocket.getLocalPort(), ldapUri.getPath(), null, null); - } - env.put(Context.PROVIDER_URL, ldapUri.toString()); - if (args[args.length - 1].equalsIgnoreCase("-trace")) { - env.put("com.sun.jndi.ldap.trace.ber", System.out); - } - - System.out.println("StoreRemote: connecting to " + ldapUri); - DirContext ctx = new InitialDirContext(env); - String dn = "cn=myremote"; - try { - Hello hello = new HelloImpl(); - ctx.bind(dn, hello); - System.out.println("StoreRemote: created entry '" + dn + "'"); + System.out.println("StoreRemote: connecting to " + ldapUri); + DirContext ctx = new InitialDirContext(env); + String dn = "cn=myremote"; - // Explicitly release the RMI object - UnicastRemoteObject.unexportObject(hello, true); + try { + Hello hello = new HelloImpl(); + ctx.bind(dn, hello); + System.out.println("StoreRemote: created entry '" + dn + "'"); + + // Explicitly release the RMI object + UnicastRemoteObject.unexportObject(hello, true); + + } catch (NameAlreadyBoundException e) { + System.err.println("StoreRemote: entry '" + dn + + "' already exists"); + cleanup(ctx, (String)null); + return; + } - } catch (NameAlreadyBoundException e) { - System.err.println("StoreRemote: entry '" + dn + - "' already exists"); - cleanup(ctx, (String)null); - return; - } + /* + * Retrieve the Remote object from the LDAP directory + */ - /* - * Retrieve the Remote object from the LDAP directory - */ + try { + Hello obj = (Hello) ctx.lookup(dn); + System.out.println("StoreRemote: retrieved object: " + obj); + System.out.println("StoreRemote: calling Hello.sayHello()...\n" + + obj.sayHello()); - try { - Hello obj = (Hello) ctx.lookup(dn); - System.out.println("StoreRemote: retrieved object: " + obj); - System.out.println("StoreRemote: calling Hello.sayHello()...\n" + - obj.sayHello()); + // Explicitly release the RMI object + UnicastRemoteObject.unexportObject(obj, true); - // Explicitly release the RMI object - UnicastRemoteObject.unexportObject(obj, true); + } catch (NamingException e) { + System.err.println("StoreRemote: error retrieving entry '" + + dn + "' " + e); + e.printStackTrace(); + cleanup(ctx, dn); + return; + } - } catch (NamingException e) { - System.err.println("StoreRemote: error retrieving entry '" + - dn + "' " + e); - e.printStackTrace(); cleanup(ctx, dn); - return; } - - cleanup(ctx, dn); } /* From eb960ff6d44af11a38f88a20db68218343725c7b Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Fri, 25 Sep 2020 12:46:08 +0000 Subject: [PATCH 13/16] 8251188: Update LDAP tests not to use wildcard addresses Reviewed-by: mbalao, andrew Backport-of: a75edc29c6ce41116cc99530aa1710efb62c6d5a --- test/jdk/ProblemList.txt | 2 - .../sun/jndi/ldap/BalancedParentheses.java | 23 ++- .../sun/jndi/ldap/DeadSSLLdapTimeoutTest.java | 170 ++++++++++++------ .../sun/jndi/ldap/DeadSSLSocketFactory.java | 93 ++++++++++ .../jndi/ldap/blits/AddTests/AddNewEntry.java | 25 ++- .../com/sun/jndi/ldap/lib/LDAPTestUtils.java | 16 +- 6 files changed, 258 insertions(+), 71 deletions(-) create mode 100644 test/jdk/com/sun/jndi/ldap/DeadSSLSocketFactory.java diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 20f74af7757..ec71b1b23e9 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -838,8 +838,6 @@ sun/tools/jhsdb/HeapDumpTest.java 8193639 solaris- # jdk_other -com/sun/jndi/ldap/DeadSSLLdapTimeoutTest.java 8169942 linux-i586,macosx-all,windows-x64 - com/sun/jndi/dns/ConfigTests/PortUnreachable.java 7164518 macosx-all javax/rmi/ssl/SSLSocketParametersTest.sh 8162906 generic-all diff --git a/test/jdk/com/sun/jndi/ldap/BalancedParentheses.java b/test/jdk/com/sun/jndi/ldap/BalancedParentheses.java index 2b62888f75a..b4ea10313d8 100644 --- a/test/jdk/com/sun/jndi/ldap/BalancedParentheses.java +++ b/test/jdk/com/sun/jndi/ldap/BalancedParentheses.java @@ -24,18 +24,23 @@ /** * @test * @bug 6449574 + * @library /test/lib * @summary Invalid ldap filter is accepted and processed */ import java.io.*; import javax.naming.*; import javax.naming.directory.*; -import java.util.Properties; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.Hashtable; import java.net.Socket; import java.net.ServerSocket; +import jdk.test.lib.net.URIBuilder; + public class BalancedParentheses { // Should we run the client or server in a separate thread? // @@ -54,7 +59,13 @@ public class BalancedParentheses { // If the server prematurely exits, serverReady will be set to true // to avoid infinite hangs. void doServerSide() throws Exception { - ServerSocket serverSock = new ServerSocket(serverPort); + // Create unbound server socket + ServerSocket serverSock = new ServerSocket(); + + // And bind it to the loopback address + SocketAddress sockAddr = new InetSocketAddress( + InetAddress.getLoopbackAddress(), 0); + serverSock.bind(sockAddr); // signal client, it's ready to accecpt connection serverPort = serverSock.getLocalPort(); @@ -106,7 +117,13 @@ void doClientSide() throws Exception { Hashtable env = new Hashtable<>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); - env.put(Context.PROVIDER_URL, "ldap://localhost:" + serverPort); + // Construct the provider URL + String providerURL = URIBuilder.newBuilder() + .scheme("ldap") + .loopback() + .port(serverPort) + .build().toString(); + env.put(Context.PROVIDER_URL, providerURL); env.put("com.sun.jndi.ldap.read.timeout", "1000"); // env.put(Context.SECURITY_AUTHENTICATION, "simple"); diff --git a/test/jdk/com/sun/jndi/ldap/DeadSSLLdapTimeoutTest.java b/test/jdk/com/sun/jndi/ldap/DeadSSLLdapTimeoutTest.java index ea056f25237..e72683391ab 100644 --- a/test/jdk/com/sun/jndi/ldap/DeadSSLLdapTimeoutTest.java +++ b/test/jdk/com/sun/jndi/ldap/DeadSSLLdapTimeoutTest.java @@ -21,34 +21,35 @@ * questions. */ -/** +/* * @test - * @run main/othervm DeadSSLLdapTimeoutTest * @bug 8141370 * @key intermittent + * @library /test/lib + * @build DeadSSLSocketFactory + * @run main/othervm DeadSSLLdapTimeoutTest */ -import java.net.Socket; +import java.io.EOFException; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; import java.net.SocketTimeoutException; -import java.io.*; -import javax.naming.*; -import javax.naming.directory.*; -import java.util.List; +import javax.naming.Context; +import javax.naming.InitialContext; +import javax.naming.NamingException; import java.util.Hashtable; -import java.util.ArrayList; import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import javax.naming.directory.InitialDirContext; import javax.net.ssl.SSLHandshakeException; -import static java.util.concurrent.TimeUnit.MILLISECONDS; +import jdk.test.lib.net.URIBuilder; + import static java.util.concurrent.TimeUnit.NANOSECONDS; @@ -57,26 +58,26 @@ class DeadServerTimeoutSSLTest implements Callable { Hashtable env; DeadSSLServer server; boolean passed = false; - private int HANGING_TEST_TIMEOUT = 20_000; public DeadServerTimeoutSSLTest(Hashtable env) throws IOException { - this.server = new DeadSSLServer(); + SocketAddress sockAddr = new InetSocketAddress( + InetAddress.getLoopbackAddress(), 0); + this.server = new DeadSSLServer(sockAddr); this.env = env; } - public void performOp(InitialContext ctx) throws NamingException {} - - public void handleNamingException(NamingException e, long start, long end) { + public void handleNamingException(NamingException e) { if (e.getCause() instanceof SocketTimeoutException || e.getCause().getCause() instanceof SocketTimeoutException) { // SSL connect will timeout via readReply using // SocketTimeoutException - e.printStackTrace(); + System.out.println("PASS: Observed expected SocketTimeoutException"); pass(); } else if (e.getCause() instanceof SSLHandshakeException && e.getCause().getCause() instanceof EOFException) { // test seems to be failing intermittently on some // platforms. + System.out.println("PASS: Observed expected SSLHandshakeException/EOFException"); pass(); } else { fail(e); @@ -92,6 +93,7 @@ public void fail() { } public void fail(Exception e) { + System.err.println("FAIL: Unexpected exception was observed:" + e.getMessage()); throw new RuntimeException("Test failed", e); } @@ -106,34 +108,36 @@ boolean shutItDown(InitialContext ctx) { public Boolean call() { InitialContext ctx = null; - ScheduledFuture killer = null; - long start = System.nanoTime(); try { - while(!server.accepting()) - Thread.sleep(200); // allow the server to start up + server.serverStarted.await(); // Wait for the server to start-up Thread.sleep(200); // to be sure - env.put(Context.PROVIDER_URL, "ldap://localhost:" + - server.getLocalPort()); + env.put(Context.PROVIDER_URL, + URIBuilder.newBuilder() + .scheme("ldap") + .loopback() + .port(server.getLocalPort()) + .buildUnchecked().toString() + ); + long start = System.nanoTime(); try { ctx = new InitialDirContext(env); - performOp(ctx); fail(); } catch (NamingException e) { long end = System.nanoTime(); System.out.println(this.getClass().toString() + " - elapsed: " + NANOSECONDS.toMillis(end - start)); - handleNamingException(e, start, end); + handleNamingException(e); } finally { - if (killer != null && !killer.isDone()) - killer.cancel(true); + // Stop the server side thread + server.testDone.countDown(); shutItDown(ctx); server.close(); } return passed; - } catch (IOException|InterruptedException e) { + } catch (IOException | InterruptedException e) { throw new RuntimeException(e); } } @@ -141,20 +145,69 @@ public Boolean call() { class DeadSSLServer extends Thread { ServerSocket serverSock; - boolean accepting = false; - - public DeadSSLServer() throws IOException { - this.serverSock = new ServerSocket(0); + // Latch to be used by client to wait for server to start + CountDownLatch serverStarted = new CountDownLatch(1); + + // Latch to be used by server thread to wait for client to finish testing + CountDownLatch testDone = new CountDownLatch(1); + + public DeadSSLServer(SocketAddress socketAddress) throws IOException { + // create unbound server socket + var srvSock = new ServerSocket(); + // bind it to the address provided + srvSock.bind(socketAddress); + this.serverSock = srvSock; start(); } public void run() { - while(true) { - try { - accepting = true; - Socket socket = serverSock.accept(); + // Signal client to proceed with the test + serverStarted.countDown(); + while (true) { + try (var acceptedSocket = serverSock.accept()) { + System.err.println("Accepted connection:" + acceptedSocket); + int iteration = 0; + // Wait for socket to get opened by DeadSSLSocketFactory and connected to the test server + while (iteration++ < 20) { + if (DeadSSLSocketFactory.firstCreatedSocket.get() != null && + DeadSSLSocketFactory.firstCreatedSocket.get().isConnected()) { + break; + } + try { + TimeUnit.MILLISECONDS.sleep(50); + } catch (InterruptedException ie) { + } + } + Socket clientSideSocket = DeadSSLSocketFactory.firstCreatedSocket.get(); + System.err.printf("Got SSLSocketFactory connection after %d iterations: %s%n", + iteration, clientSideSocket); + + if (clientSideSocket == null || !clientSideSocket.isConnected()) { + // If after 1000 ms client side connection is not opened - probably other local process + // tried to connect to the test server socket. Close current connection and retry accept. + continue; + } else { + // Check if accepted socket is connected to the LDAP client + if (acceptedSocket.getLocalPort() == clientSideSocket.getPort() && + acceptedSocket.getPort() == clientSideSocket.getLocalPort() && + acceptedSocket.getInetAddress().equals(clientSideSocket.getLocalAddress())) { + System.err.println("Accepted connection is originated from LDAP client:" + acceptedSocket); + try { + // Give LDAP client time to fully establish the connection. + // When client is done - the accepted socket will be closed + testDone.await(); + } catch (InterruptedException e) { + } + break; + } else { + // If accepted socket is not from the LDAP client - the accepted connection will be closed and new + // one will be accepted + System.err.println("SSLSocketFactory connection has been established, but originated not from" + + " the test's LDAP client:" + acceptedSocket); + } + } } catch (Exception e) { - break; + System.err.println("Server socket. Failure to accept connection:" + e.getMessage()); } } } @@ -163,28 +216,26 @@ public int getLocalPort() { return serverSock.getLocalPort(); } - public boolean accepting() { - return accepting; - } - public void close() throws IOException { serverSock.close(); } } public class DeadSSLLdapTimeoutTest { + // com.sun.jndi.ldap.connect.timeout value to set + static final String CONNECT_TIMEOUT_MS = "10"; + + // com.sun.jndi.ldap.read.timeout value to set + static final String READ_TIMEOUT_MS = "3000"; static Hashtable createEnv() { Hashtable env = new Hashtable<>(11); env.put(Context.INITIAL_CONTEXT_FACTORY, - "com.sun.jndi.ldap.LdapCtxFactory"); + "com.sun.jndi.ldap.LdapCtxFactory"); return env; } public static void main(String[] args) throws Exception { - - InitialContext ctx = null; - // // Running this test serially as it seems to tickle a problem // on older kernels @@ -193,19 +244,24 @@ public static void main(String[] args) throws Exception { // and ssl enabled // this should exit with a SocketTimeoutException as the root cause // it should also use the connect timeout instead of the read timeout - System.out.println("Running connect timeout test with 10ms connect timeout, 3000ms read timeout & SSL"); + System.out.printf("Running connect timeout test with %sms connect timeout," + + " %sms read timeout & SSL%n", + CONNECT_TIMEOUT_MS, READ_TIMEOUT_MS); + Hashtable sslenv = createEnv(); - sslenv.put("com.sun.jndi.ldap.connect.timeout", "10"); - sslenv.put("com.sun.jndi.ldap.read.timeout", "3000"); + // Setup connect timeout environment property + sslenv.put("com.sun.jndi.ldap.connect.timeout", CONNECT_TIMEOUT_MS); + // Setup read timeout environment property + sslenv.put("com.sun.jndi.ldap.read.timeout", READ_TIMEOUT_MS); + // Setup DeadSSLSocketFactory to track the client's first LDAP connection + sslenv.put("java.naming.ldap.factory.socket", "DeadSSLSocketFactory"); + // Use SSL protocol sslenv.put(Context.SECURITY_PROTOCOL, "ssl"); - boolean testFailed = - (new DeadServerTimeoutSSLTest(sslenv).call()) ? false : true; + boolean testFailed = !new DeadServerTimeoutSSLTest(sslenv).call(); if (testFailed) { throw new AssertionError("some tests failed"); } - } - } diff --git a/test/jdk/com/sun/jndi/ldap/DeadSSLSocketFactory.java b/test/jdk/com/sun/jndi/ldap/DeadSSLSocketFactory.java new file mode 100644 index 00000000000..2966abe9554 --- /dev/null +++ b/test/jdk/com/sun/jndi/ldap/DeadSSLSocketFactory.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2020, 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. + */ + +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +/* + * A custom socket factory used to override the default socket factory and track the LDAP client connection. + * Factory can create only one SSLSocket. See the DeadServerTimeoutSSLTest test. + */ +public class DeadSSLSocketFactory extends SocketFactory { + // Client socket that is used by LDAP connection + public static AtomicReference firstCreatedSocket = new AtomicReference<>(); + + // Boolean to track if connection socket has been opened + public static AtomicBoolean isConnectionOpened = new AtomicBoolean(false); + + // Default SSLSocketFactory that will be used for SSL socket creation + final SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault(); + + // Create unconnected socket + public Socket createSocket() throws IOException { + if (!isConnectionOpened.getAndSet(true)) { + System.err.println("DeadSSLSocketFactory: Creating unconnected socket"); + firstCreatedSocket.set((SSLSocket) factory.createSocket()); + return firstCreatedSocket.get(); + } else { + throw new RuntimeException("DeadSSLSocketFactory only allows creation of one SSL socket"); + } + } + + public DeadSSLSocketFactory() { + System.err.println("DeadSSLSocketFactory: Constructor call"); + } + + public static SocketFactory getDefault() { + System.err.println("DeadSSLSocketFactory: acquiring DeadSSLSocketFactory as default socket factory"); + return new DeadSSLSocketFactory(); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + // Not used by DeadSSLLdapTimeoutTest + return factory.createSocket(host, port); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, + int localPort) throws IOException { + // Not used by DeadSSLLdapTimeoutTest + return factory.createSocket(host, port, localHost, localPort); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + // Not used by DeadSSLLdapTimeoutTest + return factory.createSocket(host, port); + } + + @Override + public Socket createSocket(InetAddress address, int port, + InetAddress localAddress, int localPort) throws IOException { + // Not used by DeadSSLLdapTimeoutTest + return factory.createSocket(address, port, localAddress, localPort); + } +} + diff --git a/test/jdk/com/sun/jndi/ldap/blits/AddTests/AddNewEntry.java b/test/jdk/com/sun/jndi/ldap/blits/AddTests/AddNewEntry.java index 92528677453..3b9f7c1b9a5 100644 --- a/test/jdk/com/sun/jndi/ldap/blits/AddTests/AddNewEntry.java +++ b/test/jdk/com/sun/jndi/ldap/blits/AddTests/AddNewEntry.java @@ -27,7 +27,7 @@ * @summary Verify capability to add a new entry to the directory using the * ADD operation. * @modules java.naming/com.sun.jndi.ldap - * @library ../../lib/ /javax/naming/module/src/test/test/ + * @library /test/lib ../../lib/ /javax/naming/module/src/test/test/ * @build LDAPServer LDAPTestUtils * @run main/othervm AddNewEntry */ @@ -41,19 +41,36 @@ import javax.naming.directory.InitialDirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.ServerSocket; +import java.net.SocketAddress; import java.util.Hashtable; +import jdk.test.lib.net.URIBuilder; public class AddNewEntry { public static void main(String[] args) throws Exception { - ServerSocket serverSocket = new ServerSocket(0); + // Create unbound server socket + ServerSocket serverSocket = new ServerSocket(); + + // Bind it to the loopback address + SocketAddress sockAddr = new InetSocketAddress( + InetAddress.getLoopbackAddress(), 0); + serverSocket.bind(sockAddr); + + // Construct the provider URL for LDAPTestUtils + String providerURL = URIBuilder.newBuilder() + .scheme("ldap") + .loopback() + .port(serverSocket.getLocalPort()) + .buildUnchecked().toString(); Hashtable env; // initialize test - env = LDAPTestUtils - .initEnv(serverSocket, AddNewEntry.class.getName(), args, true); + env = LDAPTestUtils.initEnv(serverSocket, providerURL, + AddNewEntry.class.getName(), args, true); /* Build attribute set */ String[] ids = { "objectClass", "sn", "cn", "telephoneNumber", "mail", diff --git a/test/jdk/com/sun/jndi/ldap/lib/LDAPTestUtils.java b/test/jdk/com/sun/jndi/ldap/lib/LDAPTestUtils.java index dc3ec74e06c..c44cc4aed98 100644 --- a/test/jdk/com/sun/jndi/ldap/lib/LDAPTestUtils.java +++ b/test/jdk/com/sun/jndi/ldap/lib/LDAPTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2020, 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 @@ -50,12 +50,17 @@ public class LDAPTestUtils { * Process command line arguments and return properties in a Hashtable. */ public static Hashtable initEnv(String testname, - String[] args) { + String[] args) { return initEnv(null, testname, args, false); } public static Hashtable initEnv(ServerSocket socket, - String testname, String[] args, boolean authInfo) { + String testname, String[] args, boolean authInfo) { + return initEnv(socket, null, testname, args, authInfo); + } + + public static Hashtable initEnv(ServerSocket socket, String providerUrl, + String testname, String[] args, boolean authInfo) { Hashtable env = new Hashtable<>(); String root = "o=IMC,c=US"; @@ -103,8 +108,9 @@ public static Hashtable initEnv(ServerSocket socket, if (socket != null) { env.put(TEST_LDAP_SERVER_THREAD, startLDAPServer(socket, getCaptureFile(testname))); - env.put("java.naming.provider.url", - "ldap://localhost:" + socket.getLocalPort()); + String url = providerUrl != null ? providerUrl : + "ldap://localhost:" + socket.getLocalPort(); + env.put("java.naming.provider.url", url); } else { // for tests which run against remote server or no server // required From 34a77a471b260f3b6f65c6cc61609d51eeade193 Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Fri, 16 Sep 2022 10:04:51 +0000 Subject: [PATCH 14/16] 8290367: Update default value and extend the scope of com.sun.jndi.ldap.object.trustSerialData system property 8332643: Better Location requests Reviewed-by: yan, andrew Backport-of: 7765942aeee25cbeb5fd932a93b3d8f9d4ca3655 --- .../share/classes/com/sun/jndi/ldap/Obj.java | 6 +- .../com/sun/jndi/ldap/VersionHelper.java | 17 +-- .../objects/RemoteLocationAttributeTest.java | 124 ++++++++++++++++++ .../objects/RemoteLocationAttributeTest.ldap | 61 +++++++++ test/jdk/javax/naming/module/RunBasic.java | 30 +++-- .../src/test/test/ConnectWithAuthzId.java | 6 +- .../module/src/test/test/ConnectWithFoo.java | 5 +- .../module/src/test/test/ReadByUrl.java | 5 +- .../module/src/test/test/StoreFruit.java | 12 +- .../module/src/test/test/StoreObject.java | 12 +- .../module/src/test/test/StorePerson.java | 12 +- .../module/src/test/test/StoreRemote.java | 9 +- 12 files changed, 252 insertions(+), 47 deletions(-) create mode 100644 test/jdk/com/sun/jndi/ldap/objects/RemoteLocationAttributeTest.java create mode 100644 test/jdk/com/sun/jndi/ldap/objects/RemoteLocationAttributeTest.ldap diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java b/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java index 5c4b9ab0f6c..dc69b355850 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/Obj.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2022, 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,10 @@ static Object decodeObject(Attributes attrs) ClassLoader cl = helper.getURLClassLoader(codebases); return deserializeObject((byte[])attr.get(), cl); } else if ((attr = attrs.get(JAVA_ATTRIBUTES[REMOTE_LOC])) != null) { + // javaRemoteLocation attribute (RMI stub will be created) + if (!VersionHelper.isSerialDataAllowed()) { + throw new NamingException("Object deserialization is not allowed"); + } // For backward compatibility only return decodeRmiObject( (String)attrs.get(JAVA_ATTRIBUTES[CLASSNAME]).get(), diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/VersionHelper.java b/src/java.naming/share/classes/com/sun/jndi/ldap/VersionHelper.java index 93f09e569d7..acc723247b7 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/VersionHelper.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/VersionHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2022, 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,8 @@ public final class VersionHelper { private static final boolean trustURLCodebase; /** - * Determines whether objects may be deserialized from the content of - * 'javaSerializedData' attribute. + * Determines whether objects may be deserialized or reconstructed from a content of + * 'javaSerializedData', 'javaRemoteLocation' or 'javaReferenceAddress' LDAP attributes. */ private static final boolean trustSerialData; @@ -56,10 +56,10 @@ public final class VersionHelper { "com.sun.jndi.ldap.object.trustURLCodebase", "false"); trustURLCodebase = "true".equalsIgnoreCase(trust); - // System property to control whether classes is allowed to be loaded from - // 'javaSerializedData' attribute + // System property to control whether classes are allowed to be loaded from + // 'javaSerializedData', 'javaRemoteLocation' or 'javaReferenceAddress' attributes. String trustSerialDataSp = getPrivilegedProperty( - "com.sun.jndi.ldap.object.trustSerialData", "true"); + "com.sun.jndi.ldap.object.trustSerialData", "false"); trustSerialData = "true".equalsIgnoreCase(trustSerialDataSp); } @@ -80,8 +80,9 @@ static VersionHelper getVersionHelper() { } /** - * Returns true if deserialization of objects from 'javaSerializedData' - * and 'javaReferenceAddress' LDAP attributes is allowed. + * Returns true if deserialization or reconstruction of objects from + * 'javaSerializedData', 'javaRemoteLocation' and 'javaReferenceAddress' + * LDAP attributes is allowed. * * @return true if deserialization is allowed; false - otherwise */ diff --git a/test/jdk/com/sun/jndi/ldap/objects/RemoteLocationAttributeTest.java b/test/jdk/com/sun/jndi/ldap/objects/RemoteLocationAttributeTest.java new file mode 100644 index 00000000000..171844833c8 --- /dev/null +++ b/test/jdk/com/sun/jndi/ldap/objects/RemoteLocationAttributeTest.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2022, 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. + */ + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.SocketAddress; +import java.util.Hashtable; +import javax.naming.CommunicationException; +import javax.naming.NamingException; +import javax.naming.ServiceUnavailableException; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; + +import jdk.test.lib.net.URIBuilder; + +/** + * @test + * @bug 8290367 + * @summary Check if com.sun.jndi.ldap.object.trustSerialData covers the creation + * of RMI remote objects from the 'javaRemoteLocation' LDAP attribute. + * @modules java.naming/com.sun.jndi.ldap + * @library /test/lib ../lib /javax/naming/module/src/test/test/ + * @build LDAPServer LDAPTestUtils + * + * @run main/othervm RemoteLocationAttributeTest + * @run main/othervm -Dcom.sun.jndi.ldap.object.trustSerialData + * RemoteLocationAttributeTest + * @run main/othervm -Dcom.sun.jndi.ldap.object.trustSerialData=false + * RemoteLocationAttributeTest + * @run main/othervm -Dcom.sun.jndi.ldap.object.trustSerialData=true + * RemoteLocationAttributeTest + * @run main/othervm -Dcom.sun.jndi.ldap.object.trustSerialData=TrUe + * RemoteLocationAttributeTest + */ + +public class RemoteLocationAttributeTest { + + public static void main(String[] args) throws Exception { + // Create unbound server socket + ServerSocket serverSocket = new ServerSocket(); + + // Bind it to the loopback address + SocketAddress sockAddr = new InetSocketAddress( + InetAddress.getLoopbackAddress(), 0); + serverSocket.bind(sockAddr); + + // Construct the provider URL for LDAPTestUtils + String providerURL = URIBuilder.newBuilder() + .scheme("ldap") + .loopback() + .port(serverSocket.getLocalPort()) + .buildUnchecked().toString(); + + Hashtable env; + + // Initialize test environment variables + env = LDAPTestUtils.initEnv(serverSocket, providerURL, + RemoteLocationAttributeTest.class.getName(), args, false); + + DirContext ctx = null; + try (serverSocket) { + System.err.println(env); + // connect to server + ctx = new InitialDirContext(env); + Object lookupResult = ctx.lookup("Test"); + System.err.println("Lookup result:" + lookupResult); + // Test doesn't provide RMI registry running at 127.0.0.1:1097, but if + // there is one running on test host successful result is valid for + // cases when reconstruction allowed. + if (!RECONSTRUCTION_ALLOWED) { + throw new AssertionError("Unexpected successful lookup"); + } + } catch (ServiceUnavailableException | CommunicationException connectionException) { + // The remote location was properly reconstructed but connection to + // RMI endpoint failed: + // ServiceUnavailableException - no open socket on 127.0.0.1:1097 + // CommunicationException - 127.0.0.1:1097 is open, but it is not RMI registry + System.err.println("Got one of connection exceptions:" + connectionException); + if (!RECONSTRUCTION_ALLOWED) { + throw new AssertionError("Reconstruction not blocked, as expected"); + } + } catch (NamingException ne) { + String message = ne.getMessage(); + System.err.printf("Got NamingException with message: '%s'%n", message); + if (RECONSTRUCTION_ALLOWED && EXPECTED_NAMING_EXCEPTION_MESSAGE.equals(message)) { + throw new AssertionError("Reconstruction unexpectedly blocked"); + } + if (!RECONSTRUCTION_ALLOWED && !EXPECTED_NAMING_EXCEPTION_MESSAGE.equals(message)) { + throw new AssertionError("Reconstruction not blocked"); + } + } finally { + LDAPTestUtils.cleanup(ctx); + } + } + + // Reconstruction of RMI remote objects is allowed if 'com.sun.jndi.ldap.object.trustSerialData' + // is set to "true". If the system property is not specified it implies default "false" value + private static final boolean RECONSTRUCTION_ALLOWED = + Boolean.getBoolean("com.sun.jndi.ldap.object.trustSerialData"); + + // NamingException message when reconstruction is not allowed + private static final String EXPECTED_NAMING_EXCEPTION_MESSAGE = "Object deserialization is not allowed"; +} diff --git a/test/jdk/com/sun/jndi/ldap/objects/RemoteLocationAttributeTest.ldap b/test/jdk/com/sun/jndi/ldap/objects/RemoteLocationAttributeTest.ldap new file mode 100644 index 00000000000..88d69223788 --- /dev/null +++ b/test/jdk/com/sun/jndi/ldap/objects/RemoteLocationAttributeTest.ldap @@ -0,0 +1,61 @@ +# +# Copyright (c) 2022, 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. +# + +################################################################################ +# Capture file for RemoteLocationAttributeTest.java +# +# NOTE: This hexadecimal dump of LDAP protocol messages was generated by +# running the RemoteLocationAttributeTest application program against +# a real LDAP server and setting the JNDI/LDAP environment property: +# com.sun.jndi.ldap.trace.ber to activate LDAP message tracing. +# +################################################################################ + +# LDAP BindRequest +0000: 30 0C 02 01 01 60 07 02 01 03 04 00 80 00 0....`........ + +# LDAP BindResponse +0000: 30 0C 02 01 01 61 07 0A 01 00 04 00 04 00 0....a........ + +# LDAP SearchRequest +0000: 30 46 02 01 02 63 24 04 04 54 65 73 74 0A 01 00 0F...c$..Test... +0010: 0A 01 03 02 01 00 02 01 00 01 01 00 87 0B 6F 62 ..............ob +0020: 6A 65 63 74 43 6C 61 73 73 30 00 A0 1B 30 19 04 jectClass0...0.. +0030: 17 32 2E 31 36 2E 38 34 30 2E 31 2E 31 31 33 37 .2.16.840.1.1137 +0040: 33 30 2E 33 2E 34 2E 32 30.3.4.2 + +# LDAP SearchResultEntry +0000: 30 5E 02 01 02 64 59 04 04 54 65 73 74 30 51 30 0^...dY..Test0Q0 +0010: 16 04 0D 6A 61 76 61 43 6C 61 73 73 4E 61 6D 65 ...javaClassName +0020: 31 05 04 03 66 6F 6F 30 37 04 12 6A 61 76 61 52 1...foo07..javaR +0030: 65 6D 6F 74 65 4C 6F 63 61 74 69 6F 6E 31 21 04 emoteLocation1!. +0040: 1F 72 6D 69 3A 2F 2F 31 32 37 2E 30 2E 30 2E 31 .rmi://127.0.0.1 +0050: 3A 31 30 39 37 2F 54 65 73 74 52 65 6D 6F 74 65 :1097/TestRemote + +# LDAP SearchResultDone +0000: 30 0C 02 01 02 65 07 0A 01 00 04 00 04 00 0....e........ + +# LDAP UnbindRequest +0000: 30 22 02 01 03 42 00 A0 1B 30 19 04 17 32 2E 31 0"...B...0...2.1 +0010: 36 2E 38 34 30 2E 31 2E 31 31 33 37 33 30 2E 33 6.840.1.113730.3 +0020: 2E 34 2E 32 .4.2 diff --git a/test/jdk/javax/naming/module/RunBasic.java b/test/jdk/javax/naming/module/RunBasic.java index 0fd9d2b28aa..512062de409 100644 --- a/test/jdk/javax/naming/module/RunBasic.java +++ b/test/jdk/javax/naming/module/RunBasic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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.nio.file.Files; import java.nio.file.Path; import java.time.Duration; +import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; @@ -92,10 +93,14 @@ public static void main(String[] args) throws Throwable { System.out.println("Hostname: [" + HOST_NAME + "]"); // run tests - runTest("java.desktop", "test.StoreObject"); - runTest("person", "test.StorePerson"); - runTest("fruit", "test.StoreFruit"); - runTest("hello", "test.StoreRemote"); + runTest("java.desktop", "test.StoreObject", + "-Dcom.sun.jndi.ldap.object.trustSerialData=true"); + runTest("person", "test.StorePerson", + "-Dcom.sun.jndi.ldap.object.trustSerialData=true"); + runTest("fruit", "test.StoreFruit", + "-Dcom.sun.jndi.ldap.object.trustSerialData=true"); + runTest("hello", "test.StoreRemote", + "-Dcom.sun.jndi.ldap.object.trustSerialData=true"); runTest("foo", "test.ConnectWithFoo"); runTest("authz", "test.ConnectWithAuthzId"); runTest("ldapv4", "test.ReadByUrl"); @@ -117,10 +122,19 @@ private static void makeDir(String first, String... more) Files.createDirectories(Path.of(first, more)); } - private static void runTest(String desc, String clsName) throws Throwable { + private static void runTest(String desc, String clsName, String... additionalVmOpts) throws Throwable { + List opts = new ArrayList<>(); + opts.add("-Dtest.src=" + TEST_SRC); + for (String opt : additionalVmOpts) { + opts.add(opt); + } + opts.add("-p"); + opts.add("mods"); + opts.add("-m"); + opts.add("test/" + clsName); + opts.add("ldap://" + HOST_NAME + "/dc=ie,dc=oracle,dc=com"); System.out.println("Running with the '" + desc + "' module..."); - runJava("-Dtest.src=" + TEST_SRC, "-p", "mods", "-m", "test/" + clsName, - "ldap://" + HOST_NAME + "/dc=ie,dc=oracle,dc=com"); + runJava(opts.toArray(String[]::new)); } private static void runJava(String... opts) throws Throwable { diff --git a/test/jdk/javax/naming/module/src/test/test/ConnectWithAuthzId.java b/test/jdk/javax/naming/module/src/test/test/ConnectWithAuthzId.java index 5f707e44d69..3a50981f14e 100644 --- a/test/jdk/javax/naming/module/src/test/test/ConnectWithAuthzId.java +++ b/test/jdk/javax/naming/module/src/test/test/ConnectWithAuthzId.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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 @@ -33,7 +33,6 @@ import java.net.*; import java.util.*; import javax.naming.*; -import javax.naming.directory.*; import javax.naming.ldap.*; import org.example.authz.AuthzIdRequestControl; @@ -68,7 +67,7 @@ public static void main(String[] args) throws Exception { System.err.println(" is the LDAP URL of the parent entry\n"); System.err.println("example:"); System.err.println(" java ConnectWithAuthzId ldap://oasis/o=airius.com"); - return; + throw new IllegalArgumentException(); } /* @@ -134,6 +133,7 @@ public void run() { } } catch (NamingException e) { System.err.println("ConnectWithAuthzId: error connecting " + e); + throw e; } finally { if (ctx != null) { ctx.close(); diff --git a/test/jdk/javax/naming/module/src/test/test/ConnectWithFoo.java b/test/jdk/javax/naming/module/src/test/test/ConnectWithFoo.java index 6df3b97cc61..41108e2c6f6 100644 --- a/test/jdk/javax/naming/module/src/test/test/ConnectWithFoo.java +++ b/test/jdk/javax/naming/module/src/test/test/ConnectWithFoo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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 @@ -65,7 +65,7 @@ public static void main(String[] args) throws Exception { System.err.println(" is the LDAP URL of the parent entry\n"); System.err.println("example:"); System.err.println(" java ConnectWithFoo ldap://oasis/o=airius.com"); - return; + throw new IllegalArgumentException(); } /* @@ -112,6 +112,7 @@ public void run() { System.out.println("ConnectWithFoo: connected"); } catch (NamingException e) { System.err.println("ConnectWithFoo: error connecting " + e); + throw e; } finally { if (ctx != null) { ctx.close(); diff --git a/test/jdk/javax/naming/module/src/test/test/ReadByUrl.java b/test/jdk/javax/naming/module/src/test/test/ReadByUrl.java index cc2861b433b..df610dce3ca 100644 --- a/test/jdk/javax/naming/module/src/test/test/ReadByUrl.java +++ b/test/jdk/javax/naming/module/src/test/test/ReadByUrl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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 @@ -63,7 +63,7 @@ public static void main(String[] args) throws Exception { System.err.println(" is the LDAP URL of the parent entry\n"); System.err.println("example:"); System.err.println(" java ReadByUrl ldap://oasis/o=airius.com"); - return; + throw new IllegalArgumentException(); } /* @@ -112,6 +112,7 @@ public void run() { entry.close(); } catch (NamingException e) { System.err.println("ReadByUrl: error connecting " + e); + throw e; } finally { if (ctx != null) { ctx.close(); diff --git a/test/jdk/javax/naming/module/src/test/test/StoreFruit.java b/test/jdk/javax/naming/module/src/test/test/StoreFruit.java index 8121c6ac906..6eaee6b6adc 100644 --- a/test/jdk/javax/naming/module/src/test/test/StoreFruit.java +++ b/test/jdk/javax/naming/module/src/test/test/StoreFruit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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 @@ -65,7 +65,7 @@ public static void main(String[] args) throws Exception { System.err.println(" is the LDAP URL of the parent entry\n"); System.err.println("example:"); System.err.println(" java StoreFruit ldap://oasis/o=airius.com"); - return; + throw new IllegalArgumentException(); } /* @@ -116,7 +116,7 @@ public void run() { System.err.println("StoreFruit: entry '" + dn + "' already exists"); cleanup(ctx, (String)null); - return; + throw e; } try { @@ -126,7 +126,7 @@ public void run() { System.err.println("StoreFruit: entry '" + dn2 + "' already exists"); cleanup(ctx, dn); - return; + throw e; } /* @@ -141,7 +141,7 @@ public void run() { dn + "' " + e); e.printStackTrace(); cleanup(ctx, dn, dn2); - return; + throw e; } try { @@ -152,7 +152,7 @@ public void run() { dn2 + "' " + e); e.printStackTrace(); cleanup(ctx, dn, dn2); - return; + throw e; } cleanup(ctx, dn, dn2); diff --git a/test/jdk/javax/naming/module/src/test/test/StoreObject.java b/test/jdk/javax/naming/module/src/test/test/StoreObject.java index ce1625ee8d9..8e4e82d5646 100644 --- a/test/jdk/javax/naming/module/src/test/test/StoreObject.java +++ b/test/jdk/javax/naming/module/src/test/test/StoreObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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 @@ -64,7 +64,7 @@ public static void main(String[] args) throws Exception { System.err.println(" is the LDAP URL of the parent entry\n"); System.err.println("example:"); System.err.println(" java StoreObject ldap://oasis/o=airius.com"); - return; + throw new IllegalArgumentException(); } /* @@ -114,7 +114,7 @@ public void run() { System.err.println("StoreObject: entry '" + dn + "' already exists"); cleanup(ctx, (String)null); - return; + throw e; } try { @@ -124,7 +124,7 @@ public void run() { System.err.println("StoreObject: entry '" + dn2 + "' already exists"); cleanup(ctx, dn); - return; + throw e; } /* @@ -139,7 +139,7 @@ public void run() { dn + "' " + e); e.printStackTrace(); cleanup(ctx, dn, dn2); - return; + throw e; } try { @@ -150,7 +150,7 @@ public void run() { dn2 + "' " + e); e.printStackTrace(); cleanup(ctx, dn, dn2); - return; + throw e; } cleanup(ctx, dn, dn2); diff --git a/test/jdk/javax/naming/module/src/test/test/StorePerson.java b/test/jdk/javax/naming/module/src/test/test/StorePerson.java index 6a5baebe5a9..722d415f3c2 100644 --- a/test/jdk/javax/naming/module/src/test/test/StorePerson.java +++ b/test/jdk/javax/naming/module/src/test/test/StorePerson.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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 @@ -67,7 +67,7 @@ public static void main(String[] args) throws Exception { System.err.println(" is the LDAP URL of the parent entry\n"); System.err.println("example:"); System.err.println(" java StorePerson ldap://oasis/o=airius.com"); - return; + throw new IllegalArgumentException(); } /* @@ -124,7 +124,7 @@ public void run() { System.err.println("StorePerson: entry '" + dn + "' already exists"); cleanup(ctx, (String)null); - return; + throw e; } name = "Jill Smyth"; @@ -139,7 +139,7 @@ public void run() { System.err.println("StorePerson: entry '" + dn2 + "' already exists"); cleanup(ctx, dn); - return; + throw e; } /* @@ -161,7 +161,7 @@ public void run() { dn + "' " + e); e.printStackTrace(); cleanup(ctx, dn, dn2); - return; + throw e; } try { @@ -179,7 +179,7 @@ public void run() { dn2 + "' " + e); e.printStackTrace(); cleanup(ctx, dn, dn2); - return; + throw e; } cleanup(ctx, dn, dn2); diff --git a/test/jdk/javax/naming/module/src/test/test/StoreRemote.java b/test/jdk/javax/naming/module/src/test/test/StoreRemote.java index a7c14138aae..e8ee241d580 100644 --- a/test/jdk/javax/naming/module/src/test/test/StoreRemote.java +++ b/test/jdk/javax/naming/module/src/test/test/StoreRemote.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022, 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 @@ -53,7 +53,6 @@ public class StoreRemote { System.getProperty("test.src") + "/src/test/test/StoreRemote.ldap"; public static void main(String[] args) throws Exception { - /* * Process arguments */ @@ -66,7 +65,7 @@ public static void main(String[] args) throws Exception { System.err.println(" is the LDAP URL of the parent entry\n"); System.err.println("example:"); System.err.println(" java StoreRemote ldap://oasis/o=airius.com"); - return; + throw new IllegalArgumentException(); } /* @@ -120,7 +119,7 @@ public void run() { System.err.println("StoreRemote: entry '" + dn + "' already exists"); cleanup(ctx, (String)null); - return; + throw e; } /* @@ -141,7 +140,7 @@ public void run() { dn + "' " + e); e.printStackTrace(); cleanup(ctx, dn); - return; + throw e; } cleanup(ctx, dn); From 86d5188b8939c05abc2340c4c389c5fca640c485 Mon Sep 17 00:00:00 2001 From: Francisco Ferrari Bihurriet Date: Tue, 17 Sep 2024 18:45:20 +0200 Subject: [PATCH 15/16] 8332644: Improve graph optimizations Reviewed-by: mbalao, andrew Backport-of: 7c16d649a8118d2e7ee77cedba87e620c83294b4 --- src/hotspot/share/opto/loopnode.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index 5b612d42c37..bd5d19b47c8 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -1320,7 +1320,7 @@ Node *LoopLimitNode::Ideal(PhaseGVN *phase, bool can_reshape) { const TypeInt* init_t = phase->type(in(Init) )->is_int(); const TypeInt* limit_t = phase->type(in(Limit))->is_int(); - int stride_p; + jlong stride_p; jlong lim, ini; julong max; if (stride_con > 0) { @@ -1329,10 +1329,10 @@ Node *LoopLimitNode::Ideal(PhaseGVN *phase, bool can_reshape) { ini = init_t->_lo; max = (julong)max_jint; } else { - stride_p = -stride_con; + stride_p = -(jlong)stride_con; lim = init_t->_hi; ini = limit_t->_lo; - max = (julong)min_jint; + max = (julong)(juint)min_jint; // double cast to get 0x0000000080000000, not 0xffffffff80000000 } julong range = lim - ini + stride_p; if (range <= max) { From cee8535a9d3de8558b4b5028d68e397e508bef71 Mon Sep 17 00:00:00 2001 From: Andrew John Hughes Date: Mon, 7 Oct 2024 20:28:48 +0100 Subject: [PATCH 16/16] 8341675: [11u] Remove designator DEFAULT_PROMOTED_VERSION_PRE=ea for release 11.0.25 Reviewed-by: clanger --- make/autoconf/version-numbers | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make/autoconf/version-numbers b/make/autoconf/version-numbers index c02b769bf2a..fe5e0d9850f 100644 --- a/make/autoconf/version-numbers +++ b/make/autoconf/version-numbers @@ -37,7 +37,7 @@ DEFAULT_VERSION_DATE=2024-10-15 DEFAULT_VERSION_CLASSFILE_MAJOR=55 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`" DEFAULT_VERSION_CLASSFILE_MINOR=0 DEFAULT_ACCEPTABLE_BOOT_VERSIONS="10 11" -DEFAULT_PROMOTED_VERSION_PRE=ea +DEFAULT_PROMOTED_VERSION_PRE= LAUNCHER_NAME=openjdk PRODUCT_NAME=OpenJDK