From 665f16c30bbfce4589082be36163a3349b5e62bf Mon Sep 17 00:00:00 2001 From: Harshitha Onkar Date: Thu, 6 Feb 2025 18:30:39 +0000 Subject: [PATCH 1/2] 8347377: Add validation checks for ICC_Profile header fields Reviewed-by: prr, jdv --- .../java/awt/color/ICC_ColorSpace.java | 5 +- .../classes/java/awt/color/ICC_Profile.java | 65 ++++- .../ValidateICCHeaderData.java | 261 ++++++++++++++++++ .../ValidateICCHeaderData/invalidSRGB.icc | Bin 0 -> 6876 bytes 4 files changed, 328 insertions(+), 3 deletions(-) create mode 100644 test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/ValidateICCHeaderData.java create mode 100644 test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/invalidSRGB.icc diff --git a/src/java.desktop/share/classes/java/awt/color/ICC_ColorSpace.java b/src/java.desktop/share/classes/java/awt/color/ICC_ColorSpace.java index acb31fbca9c..071506ca460 100644 --- a/src/java.desktop/share/classes/java/awt/color/ICC_ColorSpace.java +++ b/src/java.desktop/share/classes/java/awt/color/ICC_ColorSpace.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -140,10 +140,11 @@ public ICC_ColorSpace(ICC_Profile profile) { if (profileClass != ICC_Profile.CLASS_INPUT && profileClass != ICC_Profile.CLASS_DISPLAY && profileClass != ICC_Profile.CLASS_OUTPUT + && profileClass != ICC_Profile.CLASS_DEVICELINK && profileClass != ICC_Profile.CLASS_COLORSPACECONVERSION && profileClass != ICC_Profile.CLASS_NAMEDCOLOR && profileClass != ICC_Profile.CLASS_ABSTRACT) { - throw new IllegalArgumentException("Invalid profile type"); + throw new IllegalArgumentException("Invalid profile class"); } thisProfile = profile; diff --git a/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java b/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java index 0d31d25b119..4fb8b179e92 100644 --- a/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java +++ b/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, 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 @@ -759,6 +759,7 @@ private interface BuiltInProfile { */ public static final int icXYZNumberX = 8; + private static final int HEADER_SIZE = 128; /** * Constructs an {@code ICC_Profile} object with a given ID. @@ -789,10 +790,15 @@ public static ICC_Profile getInstance(byte[] data) { ProfileDataVerifier.verify(data); Profile p; try { + byte[] theHeader = new byte[HEADER_SIZE]; + System.arraycopy(data, 0, theHeader, 0, HEADER_SIZE); + verifyHeader(theHeader); + p = CMSManager.getModule().loadProfile(data); } catch (CMMException c) { throw new IllegalArgumentException("Invalid ICC Profile Data"); } + try { if (getColorSpaceType(p) == ColorSpace.TYPE_GRAY && getData(p, icSigMediaWhitePointTag) != null @@ -978,6 +984,10 @@ public int getProfileClass() { return info.profileClass; } byte[] theHeader = getData(icSigHead); + return getProfileClass(theHeader); + } + + private static int getProfileClass(byte[] theHeader) { int theClassSig = intFromBigEndian(theHeader, icHdrDeviceClass); return switch (theClassSig) { case icSigInputClass -> CLASS_INPUT; @@ -1019,6 +1029,11 @@ private static int getColorSpaceType(Profile p) { return iccCStoJCS(theColorSpaceSig); } + private static int getColorSpaceType(byte[] theHeader) { + int theColorSpaceSig = intFromBigEndian(theHeader, icHdrColorSpace); + return iccCStoJCS(theColorSpaceSig); + } + /** * Returns the color space type of the Profile Connection Space (PCS). * Returns one of the color space type constants defined by the ColorSpace @@ -1038,6 +1053,21 @@ public int getPCSType() { return iccCStoJCS(thePCSSig); } + private static int getPCSType(byte[] theHeader) { + int thePCSSig = intFromBigEndian(theHeader, icHdrPcs); + int theDeviceClass = intFromBigEndian(theHeader, icHdrDeviceClass); + + if (theDeviceClass == icSigLinkClass) { + return iccCStoJCS(thePCSSig); + } else { + return switch (thePCSSig) { + case icSigXYZData -> ColorSpace.TYPE_XYZ; + case icSigLabData -> ColorSpace.TYPE_Lab; + default -> throw new IllegalArgumentException("Unexpected PCS type"); + }; + } + } + /** * Write this {@code ICC_Profile} to a file. * @@ -1118,9 +1148,42 @@ private static byte[] getData(Profile p, int tagSignature) { * @see #getData */ public void setData(int tagSignature, byte[] tagData) { + if (tagSignature == ICC_Profile.icSigHead) { + verifyHeader(tagData); + } CMSManager.getModule().setTagData(cmmProfile(), tagSignature, tagData); } + private static void verifyHeader(byte[] data) { + if (data == null || data.length < HEADER_SIZE) { + throw new IllegalArgumentException("Invalid header data"); + } + getProfileClass(data); + getColorSpaceType(data); + getPCSType(data); + checkRenderingIntent(data); + } + + private static boolean checkRenderingIntent(byte[] header) { + int index = ICC_Profile.icHdrRenderingIntent; + + /* According to ICC spec, only the least-significant 16 bits shall be + * used to encode the rendering intent. The most significant 16 bits + * shall be set to zero. Thus, we are ignoring two most significant + * bytes here. Please refer ICC Spec Document for more details. + */ + int renderingIntent = ((header[index+2] & 0xff) << 8) | + (header[index+3] & 0xff); + + switch (renderingIntent) { + case icPerceptual, icMediaRelativeColorimetric, + icSaturation, icAbsoluteColorimetric -> { + return true; + } + default -> throw new IllegalArgumentException("Unknown Rendering Intent"); + } + } + /** * Returns the number of color components in the "input" color space of this * profile. For example if the color space type of this profile is diff --git a/test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/ValidateICCHeaderData.java b/test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/ValidateICCHeaderData.java new file mode 100644 index 00000000000..28831a422b0 --- /dev/null +++ b/test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/ValidateICCHeaderData.java @@ -0,0 +1,261 @@ +/* + * Copyright (c) 2025, 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 8337703 + * @summary To verify if ICC_Profile's setData() and getInstance() methods + * validate header data and throw IAE for invalid values. + * @run main ValidateICCHeaderData + */ + +import java.awt.color.ColorSpace; +import java.awt.color.ICC_Profile; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.math.BigInteger; +import java.nio.ByteBuffer; + +public class ValidateICCHeaderData { + private static ICC_Profile profile; + + private static final boolean DEBUG = false; + private static final int VALID_HEADER_SIZE = 128; + private static final int HEADER_TAG = ICC_Profile.icSigHead; + private static final int PROFILE_CLASS_START_INDEX = ICC_Profile.icHdrDeviceClass; + private static final int COLOR_SPACE_START_INDEX = ICC_Profile.icHdrColorSpace; + private static final int RENDER_INTENT_START_INDEX = ICC_Profile.icHdrRenderingIntent; + private static final int PCS_START_INDEX = ICC_Profile.icHdrPcs; + + private static final int[] VALID_PROFILE_CLASS = new int[] { + ICC_Profile.icSigInputClass, ICC_Profile.icSigDisplayClass, + ICC_Profile.icSigOutputClass, ICC_Profile.icSigLinkClass, + ICC_Profile.icSigAbstractClass, ICC_Profile.icSigColorSpaceClass, + ICC_Profile.icSigNamedColorClass + }; + + private static final int[] VALID_COLOR_SPACE = new int[] { + ICC_Profile.icSigXYZData, ICC_Profile.icSigLabData, + ICC_Profile.icSigLuvData, ICC_Profile.icSigYCbCrData, + ICC_Profile.icSigYxyData, ICC_Profile.icSigRgbData, + ICC_Profile.icSigGrayData, ICC_Profile.icSigHsvData, + ICC_Profile.icSigHlsData, ICC_Profile.icSigCmykData, + ICC_Profile.icSigSpace2CLR, ICC_Profile.icSigSpace3CLR, + ICC_Profile.icSigSpace4CLR, ICC_Profile.icSigSpace5CLR, + ICC_Profile.icSigSpace6CLR, ICC_Profile.icSigSpace7CLR, + ICC_Profile.icSigSpace8CLR, ICC_Profile.icSigSpace9CLR, + ICC_Profile.icSigSpaceACLR, ICC_Profile.icSigSpaceBCLR, + ICC_Profile.icSigSpaceCCLR, ICC_Profile.icSigSpaceDCLR, + ICC_Profile.icSigSpaceECLR, ICC_Profile.icSigSpaceFCLR, + ICC_Profile.icSigCmyData + }; + + private static final int[] VALID_RENDER_INTENT = new int[] { + ICC_Profile.icPerceptual, ICC_Profile.icMediaRelativeColorimetric, + ICC_Profile.icSaturation, ICC_Profile.icAbsoluteColorimetric + }; + + private static void createCopyOfBuiltInProfile() { + ICC_Profile builtInProfile = ICC_Profile.getInstance(ColorSpace.CS_sRGB); + //copy of SRGB BuiltIn Profile that can be modified + //using ICC_Profile.setData() + profile = ICC_Profile.getInstance(builtInProfile.getData()); + } + + public static void main(String[] args) throws Exception { + createCopyOfBuiltInProfile(); + + System.out.println("CASE 1: Testing VALID Profile Classes ..."); + testValidHeaderData(VALID_PROFILE_CLASS, PROFILE_CLASS_START_INDEX, 4); + System.out.println("CASE 1: Passed \n"); + + // PCS field validation for Profile class != DEVICE_LINK + System.out.println("CASE 2: Testing VALID PCS Type" + + " for Profile class != DEVICE_LINK ..."); + testValidHeaderData(new int[] {ICC_Profile.icSigXYZData, ICC_Profile.icSigLabData}, + PCS_START_INDEX, 4); + System.out.println("CASE 2: Passed \n"); + + System.out.println("CASE 3: Testing INVALID PCS Type" + + " for Profile class != DEVICE_LINK ..."); + testInvalidHeaderData(ICC_Profile.icSigCmykData, PCS_START_INDEX, 4); + System.out.println("CASE 3: Passed \n"); + + System.out.println("CASE 4: Testing DEVICE LINK PROFILE CLASS ..."); + testValidHeaderData(new int[] {ICC_Profile.icSigLinkClass}, + PROFILE_CLASS_START_INDEX, 4); + //to check if instantiating BufferedImage with + //ICC_Profile device class = CLASS_DEVICELINK does not throw IAE. + BufferedImage img = new BufferedImage(100, 100, + BufferedImage.TYPE_3BYTE_BGR); + System.out.println("CASE 4: Passed \n"); + + // PCS field validation for Profile class == DEVICE_LINK + System.out.println("CASE 5: Testing VALID PCS Type" + + " for Profile class == DEVICE_LINK ..."); + testValidHeaderData(VALID_COLOR_SPACE, PCS_START_INDEX, 4); + System.out.println("CASE 5: Passed \n"); + + System.out.println("CASE 6: Testing INVALID PCS Type" + + " for Profile class == DEVICE_LINK ..."); + //original icSigLabData = 0x4C616220 + int invalidSigLabData = 0x4C616221; + testInvalidHeaderData(invalidSigLabData, PCS_START_INDEX, 4); + System.out.println("CASE 6: Passed \n"); + + System.out.println("CASE 7: Testing VALID Color Spaces ..."); + testValidHeaderData(VALID_COLOR_SPACE, COLOR_SPACE_START_INDEX, 4); + System.out.println("CASE 7: Passed \n"); + + System.out.println("CASE 8: Testing VALID Rendering Intent ..."); + testValidHeaderData(VALID_RENDER_INTENT, RENDER_INTENT_START_INDEX, 4); + System.out.println("CASE 8: Passed \n"); + + System.out.println("CASE 9: Testing INVALID Profile Class ..."); + //original icSigInputClass = 0x73636E72 + int invalidSigInputClass = 0x73636E70; + testInvalidHeaderData(invalidSigInputClass, PROFILE_CLASS_START_INDEX, 4); + System.out.println("CASE 9: Passed \n"); + + System.out.println("CASE 10: Testing INVALID Color Space ..."); + //original icSigXYZData = 0x58595A20 + int invalidSigXYZData = 0x58595A21; + testInvalidHeaderData(invalidSigXYZData, COLOR_SPACE_START_INDEX, 4); + System.out.println("CASE 10: Passed \n"); + + System.out.println("CASE 11: Testing INVALID Rendering Intent ..."); + //valid rendering intent values are 0-3 + int invalidRenderIntent = 5; + testInvalidHeaderData(invalidRenderIntent, RENDER_INTENT_START_INDEX, 4); + System.out.println("CASE 11: Passed \n"); + + System.out.println("CASE 12: Testing INVALID Header Size ..."); + testInvalidHeaderSize(); + System.out.println("CASE 12: Passed \n"); + + System.out.println("CASE 13: Testing ICC_Profile.getInstance(..)" + + " with VALID profile data ..."); + testProfileCreation(true); + System.out.println("CASE 13: Passed \n"); + + System.out.println("CASE 14: Testing ICC_Profile.getInstance(..)" + + " with INVALID profile data ..."); + testProfileCreation(false); + System.out.println("CASE 14: Passed \n"); + + System.out.println("CASE 15: Testing Deserialization of ICC_Profile ..."); + testDeserialization(); + System.out.println("CASE 15: Passed \n"); + + System.out.println("Successfully completed testing all 15 cases. Test Passed !!"); + } + + private static void testValidHeaderData(int[] validData, int startIndex, + int fieldLength) { + for (int value : validData) { + setTag(value, startIndex, fieldLength); + } + } + + private static void testInvalidHeaderData(int invalidData, int startIndex, + int fieldLength) { + try { + setTag(invalidData, startIndex, fieldLength); + throw new RuntimeException("Test Failed ! Expected IAE NOT thrown"); + } catch (IllegalArgumentException iae) { + System.out.println("Expected IAE thrown: " + iae.getMessage()); + } + } + + private static void setTag(int value, int startIndex, int fieldLength) { + byte[] byteArray; + if (startIndex == RENDER_INTENT_START_INDEX) { + byteArray = ByteBuffer.allocate(4).putInt(value).array(); + } else { + BigInteger big = BigInteger.valueOf(value); + byteArray = (big.toByteArray()); + } + + if (DEBUG) { + System.out.print("Byte Array : "); + for (int i = 0; i < byteArray.length; i++) { + System.out.print(byteArray[i] + " "); + } + System.out.println("\n"); + } + + byte[] iccProfileHeaderData = profile.getData(HEADER_TAG); + System.arraycopy(byteArray, 0, iccProfileHeaderData, startIndex, fieldLength); + profile.setData(HEADER_TAG, iccProfileHeaderData); + } + + private static void testProfileCreation(boolean validCase) { + ICC_Profile builtInProfile = ICC_Profile.getInstance(ColorSpace.CS_GRAY); + byte[] profileData = builtInProfile.getData(); + + int validDeviceClass = ICC_Profile.icSigInputClass; + BigInteger big = BigInteger.valueOf(validDeviceClass); + //valid case set device class to 0x73636E72 (icSigInputClass) + //invalid case set device class to 0x00000000 + byte[] field = validCase ? big.toByteArray() + : ByteBuffer.allocate(4).putInt(0).array(); + System.arraycopy(field, 0, profileData, PROFILE_CLASS_START_INDEX, 4); + + try { + ICC_Profile.getInstance(profileData); + if (!validCase) { + throw new RuntimeException("Test Failed ! Expected IAE NOT thrown"); + } + } catch (IllegalArgumentException iae) { + if (!validCase) { + System.out.println("Expected IAE thrown: " + iae.getMessage()); + } else { + throw new RuntimeException("Unexpected IAE thrown"); + } + } + } + + private static void testInvalidHeaderSize() { + byte[] iccProfileHeaderData = profile.getData(HEADER_TAG); + byte[] invalidHeaderSize = new byte[VALID_HEADER_SIZE - 1]; + System.arraycopy(iccProfileHeaderData, 0, + invalidHeaderSize, 0, invalidHeaderSize.length); + try { + profile.setData(HEADER_TAG, invalidHeaderSize); + throw new RuntimeException("Test Failed ! Expected IAE NOT thrown"); + } catch (IllegalArgumentException iae) { + System.out.println("Expected IAE thrown: " + iae.getMessage()); + } + } + + private static void testDeserialization() throws IOException { + //invalidSRGB.icc is serialized on older version of JDK + //Upon deserialization, the invalid profile is expected to throw IAE + try { + ICC_Profile.getInstance("./invalidSRGB.icc"); + throw new RuntimeException("Test Failed ! Expected IAE NOT thrown"); + } catch (IllegalArgumentException iae) { + System.out.println("Expected IAE thrown: " + iae.getMessage()); + } + } +} diff --git a/test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/invalidSRGB.icc b/test/jdk/java/awt/color/ICC_Profile/ValidateICCHeaderData/invalidSRGB.icc new file mode 100644 index 0000000000000000000000000000000000000000..1520dac1f1a0ce3511a636b9f0fb75c7b3026f8c GIT binary patch literal 6876 zcmeI1S5y?q8po@IU+(ln^{>DBPJPwgbx!@yzW|UKPhcme z!unw4@^Q9P3E`_$iAuB-!yUh#KqGyfm3-T;7<{2fz0 z41nlv06Kd9jzt^?ptA%3spstUl#K7=p#(QHAOKN71JXbqr~nP10}O#FumCo|5x4;_ z;0J=iM!*I!AQ7a1OppV1fqYO1O29!-1!_S9XacR^Ea(Im!Bubr+yVE&2zUy3U<$kj z^WXzmf*=SBks&H14JklskTzrpu^=nR5%Pe1pkOEriiWsQI+O$Lh6C6>vR#3hscf z!gt^impj=S?C^jk?wH;N2szRMab)foC4^R`RdDJ&F8O=azqs`H-=pb}7Iuo6bE=M<_ z+tJt2Bj_pg2Mh*7$1pLb7#B=1CJvL0DZ(7av|+AbhA@+u1uPaTjn&3lV!g28*feZD zwi0^^dl@^7ox*;^5pfDQL!1*X1johY;>vN&xE|avZW_0Q7sIRJ&GBA%4t^`X7~g>J z#^1wF;g<;F1SY|f;75of>>^YUS_#()PjpTUFQzVLFBUGgU94K{g4meYf;d&&K-^0_QM^dJS^Spx z3S*#PSRE~QgW|ktK_icqLi$ZjZ~ymfmEB+h}36k zMQKOrcIF-ZGgoM`W(c%*oPZEo9lU1+r&lpD$yLf-lbe&L%iGAu$(PCZ$j>TJ6|59u70MJYE4)&aP_$J{P^?hwQ=C^~D7h%5 zE7d6tDlI89mHm}-mD`k`sSs3HDp4wBD!nT6stT%}syV7Hs!!DLYAm%FwL@zCYK!Vj z^+5GK>YeH{Oev--GmF{69M>RgSZgF})N4G{#AvcK<24U!-q(V)jJ0C4s9^+o)uO_A@ zDJHEZv!<%1VWu^vV=NkL9jl0S(+q89XST!aqS+^N6Z16lv*v$T=vc&CG+WH9VXlc> z)39dJQrR-xvd)rcrDzpqb<~Pytz;c;U2pxuM#Y9>(`Ykot7#i&d&>5WoxWX)UAx_) zy}A81`zsEJgR?_{!=NL@(ciJk@wt<-Q?yfy)4a2>bGGvp7qpAJ%YK(JS20{uNSZ{D(bC`mghKZ%!Yo_sKQKE*wy zF%?SPklK|-<8RCZ=^E)jq)%j6WmIQ;%nZmpw?%vlcgw(5t*v{vPG>n~9nVH(v$K11 zlyY|EOl-5=R<|A69ofxEl&9*aG-Kcaq&{c&*b>b;eFzZGx_ZWrnomKT00Vi(>1$>67opO*JU?HepM zDLztyDdCok?zi55vQ)e@t90^!=Yftg#j=93_Xjr}yj5;gUVDgeDE$zx!lR1Bly8pAo&t0uLt#xhmw!+ik zbo%L6XTr{mo^?OldyaLk^}O2ons#b?K?l^awPU_Brjyqd+%=&O0V|ItGjf+V2|QZ67ol?6_xouj{_q{qCVPLp{UR!&e_TJh<`D z_2He7wIf5L{-ck^LdSTIIFDu?Cp>=pB;(1_)1Bk!@q%Y!&&r=Op4b1X`D+``gm-De zVPfEg?~A9C?8(=^rT+G1D(@xvW%;zibkmIC%*9!!+54~7znXfT^m=J7|9A1v9|QA2^HXnA-Y)-H^iKNSiT6hDdl%L%JpYjRVQI18qx8puEUdsG} z{ZjE&^K17v_iy9NiOb7EEg;kaLM#eFCg> Date: Thu, 10 Apr 2025 19:55:29 +0000 Subject: [PATCH 2/2] 8346465: Add a check in setData() to restrict the update of Built-In ICC_Profiles Reviewed-by: aivanov, jdv, prr, serb --- .../classes/java/awt/color/ICC_Profile.java | 37 +++- .../BuiltInProfileCheck.java | 168 ++++++++++++++++++ .../BuiltInProfileCheck/builtIn.icc | Bin 0 -> 141 bytes .../BuiltInProfileCheck/custom.icc | Bin 0 -> 7030 bytes .../awt/color/ICC_Profile/SetHeaderInfo.java | 6 +- .../awt/color/ICC_ProfileSetNullDataTest.java | 34 ++-- .../sun/java2d/cmm/ProfileOp/SetDataTest.java | 6 +- 7 files changed, 228 insertions(+), 23 deletions(-) create mode 100644 test/jdk/java/awt/color/ICC_Profile/BuiltInProfileCheck/BuiltInProfileCheck.java create mode 100644 test/jdk/java/awt/color/ICC_Profile/BuiltInProfileCheck/builtIn.icc create mode 100644 test/jdk/java/awt/color/ICC_Profile/BuiltInProfileCheck/custom.icc diff --git a/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java b/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java index 4fb8b179e92..0edf7c1c8c0 100644 --- a/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java +++ b/src/java.desktop/share/classes/java/awt/color/ICC_Profile.java @@ -110,6 +110,14 @@ public sealed class ICC_Profile implements Serializable */ private transient volatile ProfileDeferralInfo deferralInfo; + + /** + * Set to {@code true} for {@code BuiltInProfile}, {@code false} otherwise. + * This flag is used in {@link #setData(int, byte[])} to prevent modifying + * built-in profiles. + */ + private final transient boolean builtIn; + /** * The lazy registry of singleton profile objects for specific built-in * color spaces defined in the ColorSpace class (e.g. CS_sRGB), @@ -117,8 +125,8 @@ public sealed class ICC_Profile implements Serializable */ private interface BuiltInProfile { /* - * Deferral is only used for standard profiles. Enabling the appropriate - * access privileges is handled at a lower level. + * ProfileDeferralInfo is used for built-in profile creation only, + * and all built-in profiles should be constructed using it. */ ICC_Profile SRGB = new ICC_ProfileRGB(new ProfileDeferralInfo( "sRGB.pf", ColorSpace.TYPE_RGB, 3, CLASS_DISPLAY)); @@ -766,14 +774,20 @@ private interface BuiltInProfile { */ ICC_Profile(Profile p) { cmmProfile = p; + builtIn = false; } /** * Constructs an {@code ICC_Profile} object whose loading will be deferred. * The ID will be 0 until the profile is loaded. + * + *

+ * Note: {@code ProfileDeferralInfo} is used for built-in profile + * creation only, and all built-in profiles should be constructed using it. */ ICC_Profile(ProfileDeferralInfo pdi) { deferralInfo = pdi; + builtIn = true; } /** @@ -1137,17 +1151,34 @@ private static byte[] getData(Profile p, int tagSignature) { * This method is useful for advanced applications which need to access * profile data directly. * + *

+ * Note: JDK built-in ICC Profiles cannot be updated using this method + * as it will result in {@code IllegalArgumentException}. JDK built-in + * profiles are those obtained by {@code ICC_Profile.getInstance(int colorSpaceID)} + * where {@code colorSpaceID} is one of the following: + * {@link ColorSpace#CS_sRGB}, {@link ColorSpace#CS_LINEAR_RGB}, + * {@link ColorSpace#CS_PYCC}, {@link ColorSpace#CS_GRAY} or + * {@link ColorSpace#CS_CIEXYZ}. + * * @param tagSignature the ICC tag signature for the data element you want * to set * @param tagData the data to set for the specified tag signature * @throws IllegalArgumentException if {@code tagSignature} is not a * signature as defined in the ICC specification. - * @throws IllegalArgumentException if a content of the {@code tagData} + * @throws IllegalArgumentException if the content of the {@code tagData} * array can not be interpreted as valid tag data, corresponding to * the {@code tagSignature} + * @throws IllegalArgumentException if this is a built-in profile for one + * of the pre-defined color spaces, that is those which can be obtained + * by calling {@code ICC_Profile.getInstance(int colorSpaceID)} * @see #getData + * @see ColorSpace */ public void setData(int tagSignature, byte[] tagData) { + if (builtIn) { + throw new IllegalArgumentException("Built-in profile cannot be modified"); + } + if (tagSignature == ICC_Profile.icSigHead) { verifyHeader(tagData); } diff --git a/test/jdk/java/awt/color/ICC_Profile/BuiltInProfileCheck/BuiltInProfileCheck.java b/test/jdk/java/awt/color/ICC_Profile/BuiltInProfileCheck/BuiltInProfileCheck.java new file mode 100644 index 00000000000..8f00ab10747 --- /dev/null +++ b/test/jdk/java/awt/color/ICC_Profile/BuiltInProfileCheck/BuiltInProfileCheck.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2025, 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 8346465 + * @summary Tests if setData() throws IAE for BuiltIn profiles + */ + +import java.awt.color.ColorSpace; +import java.awt.color.ICC_Profile; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Map; + +public class BuiltInProfileCheck { + private static final int HEADER_TAG = ICC_Profile.icSigHead; + private static final int INDEX = ICC_Profile.icHdrDeviceClass; + private static final String EXCEPTION_MSG = "Built-in profile cannot be modified"; + /** + * {@link #prepareTestProfile(String, boolean, int)} + * stores the profile to test in testProfile. + */ + private static ICC_Profile testProfile; + + private static final Map colorSpace = Map.of( + ColorSpace.CS_sRGB, "CS_sRGB", + ColorSpace.CS_PYCC, "CS_PYCC", + ColorSpace.CS_GRAY, "CS_GRAY", + ColorSpace.CS_CIEXYZ, "CS_CIEXYZ", + ColorSpace.CS_LINEAR_RGB, "CS_LINEAR_RGB" + ); + + public static void main(String[] args) throws Exception { + System.out.println("CASE 1: Testing BuiltIn Profile"); + for (int cs : colorSpace.keySet()) { + prepareTestProfile("Default", true, cs); + testProfile(true, cs); + } + System.out.println("Passed\n"); + + System.out.println("CASE 2: Testing Custom Profile"); + prepareTestProfile("Default", false, ColorSpace.CS_sRGB); + testProfile(false, ColorSpace.CS_sRGB); + System.out.println("Passed\n"); + + System.out.println("CASE 3: Testing Built-In Profile" + + " Serialization & Deserialization"); + for (int cs : colorSpace.keySet()) { + prepareTestProfile("Serialize", true, cs); + testProfile(true, cs); + } + System.out.println("Passed\n"); + + System.out.println("CASE 4: Testing Custom Profile" + + " Serialization & Deserialization"); + prepareTestProfile("Serialize", false, ColorSpace.CS_sRGB); + testProfile(false, ColorSpace.CS_sRGB); + System.out.println("Passed\n"); + + System.out.println("CASE 5: Test reading Built-In profile from .icc file"); + prepareTestProfile("ReadFromFile", true, ColorSpace.CS_sRGB); + testProfile(true, ColorSpace.CS_sRGB); + System.out.println("Passed\n"); + + System.out.println("CASE 6: Test reading Custom profile from .icc file"); + prepareTestProfile("ReadFromFile", false, ColorSpace.CS_sRGB); + testProfile(false, ColorSpace.CS_sRGB); + System.out.println("Passed\n"); + } + + private static void prepareTestProfile(String testCase, + boolean isBuiltIn, int cs) { + ICC_Profile builtInProfile = ICC_Profile.getInstance(cs); + // if isBuiltIn=true use builtInProfile else create a copy + testProfile = isBuiltIn + ? builtInProfile + : ICC_Profile.getInstance(builtInProfile.getData()); + + switch (testCase) { + case "Default" -> { + // empty case block + // no further processing of testProfile required for default case + } + case "Serialize" -> { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(baos)) { + oos.writeObject(testProfile); + + byte[] array = baos.toByteArray(); + try (ObjectInputStream ois = + new ObjectInputStream(new ByteArrayInputStream(array))) { + testProfile = (ICC_Profile) ois.readObject(); + } + } catch (Exception e) { + throw new RuntimeException("Test Failed ! Serial-Deserialization" + + " case failed", e); + } + } + case "ReadFromFile" -> { + // .icc files serialized on older JDK version + String filename = isBuiltIn ? "builtIn.icc" : "custom.icc"; + String testDir = System.getProperty("test.src") + + System.getProperty("file.separator"); + filename = testDir + filename; + + try (FileInputStream fileIn = new FileInputStream(filename); + ObjectInputStream ois = new ObjectInputStream(fileIn)) { + testProfile = (ICC_Profile) ois.readObject(); + } catch (Exception e) { + throw new RuntimeException("Test Failed ! Unable to fetch" + + " .icc files", e); + } + } + } + } + + private static void testProfile(boolean isBuiltIn, int cs) { + byte[] headerData = testProfile.getData(HEADER_TAG); + // Set profile class to valid icSigInputClass = 0x73636E72 + headerData[INDEX] = 0x73; + headerData[INDEX + 1] = 0x63; + headerData[INDEX + 2] = 0x6E; + headerData[INDEX + 3] = 0x72; + + if (isBuiltIn) { + System.out.println("Testing: " + colorSpace.get(cs)); + try { + // Try updating a built-in profile, IAE is expected + testProfile.setData(HEADER_TAG, headerData); + throw new RuntimeException("Test Failed! IAE NOT thrown for profile " + + colorSpace.get(cs)); + } catch (IllegalArgumentException iae) { + if (!iae.getMessage().equals(EXCEPTION_MSG)) { + throw new RuntimeException("Test Failed! IAE with exception msg \"" + + EXCEPTION_MSG + "\" NOT thrown for profile " + + colorSpace.get(cs)); + } + } + } else { + // Modifying custom profile should NOT throw IAE + testProfile.setData(HEADER_TAG, headerData); + } + } +} diff --git a/test/jdk/java/awt/color/ICC_Profile/BuiltInProfileCheck/builtIn.icc b/test/jdk/java/awt/color/ICC_Profile/BuiltInProfileCheck/builtIn.icc new file mode 100644 index 0000000000000000000000000000000000000000..dbe11f2c6d651974009b5c4d96db2f45923a47dc GIT binary patch literal 141 zcmZ4UmVvdnh(R_hu`E$9vAjetIX@@ANYB&RIX<8$KP@vSHOSqmj6-netmDhsm>3u; ziWsDDt34S$WyATC@12+#7(E%}Gn12{W(21eWhUliR;8x6B$gzGr4|)u=I2!uFfcGM QmN2k82ger!Z7HY#0O%4im;e9( literal 0 HcmV?d00001 diff --git a/test/jdk/java/awt/color/ICC_Profile/BuiltInProfileCheck/custom.icc b/test/jdk/java/awt/color/ICC_Profile/BuiltInProfileCheck/custom.icc new file mode 100644 index 0000000000000000000000000000000000000000..b32c838c80ad94b311dcd5af73d74c1ad0ec9dd6 GIT binary patch literal 7030 zcmeHMXHXQ`8ok{;IS^)G2$FLU1SAX~IVZ_cRE8PC5C$BgU_=&IUxKzEQuMOnpFL`CJz=&RbduipE&_5Qr+s_uLG^jF{a-R@h}r_OEt9iUUV zKt7I<&d_IMr0O%15|X(3ZVnEceYi={?1U&k7yERacu(bEBL)T_lM7`3d+bnf>8YDT zuj~;3xdBBslljja{!v^uBY~Y2#d2h%GJ>MGDeNRpW-1Yfc!PcG8>#mgTmVl{ zg3rrN3C;v&VhU^kKq4oV%Wp#|Bs5G3I{`>Q4p0CV&=|~=WG{c`0Dc}Ih!u=~J>E?K zNWk3I`_Iq+8vSo~L{=h)#cvVE=i^vWDNH_2<>OdZB1^#Ydjzo=smXi}cI0CTF26xO z4iVtUZ#Y4KW4_^CUtM*xUv z2_~V$=A=e(IgAADujJo(`IlNEDS{UsnZ`~?)njx1&LaF=|EoHJ_42LtH&$QQ0YoeR zlS|@%1Nut;`$D02G5mpc2%8de8*gz-iD0&Vq~J z3b+n#fpPEv@W3p10T#eJuna*E79vAbNCr}b)FEBS2%9P$DlLNdFV291G)o!89k1kMZd#fFf@!N#th?%3B|-=vN462Bbau~1SgMz1V%zr!c8Jh zqCnzDi4lnfNs6SgWRPU0WR>JONgfTMY0%tg3A7^GY1#zsqm;aqom7<6KB+dTF{vf# zRnoT7QPTO+?b74YA7zweoMhr<4#;%LJd{Pr>d5-aX2>3uy(~MwN@A7eD(0&ERi{_o zmxJYWlAl+gDcCB+DU>SoDaC@m<aul@=#H*NuQp%JSzWh!Qjer(ryfl^BqT=C{BS+l}?YGRh(m-TbvhMOkA>E zF1VsyJzNjCPP)mvG2NQn7TnF;x4RE`kUac7Dm@;1>UeTJd%a*Ucdvt9Q|r~&bJllx z18+C)gWeB)G<~-C{Nju8_3^Fped1^6x6N@9ZDPw}=h+VIO7`nG*SMOvrFif7#`u+l;Dq)>Tw+9G4~N2u=L{q%BxNRz zChI2WCG)meY&o=Lf$PC-NP$u|rgW!D@PEw1Xr&p!F&j`vmlPR9b$sFFQ zvvuFrr&-QfC$dr5%y>^ixduseSDjU0oV zlAO1Dg7@_1%IEINo%zn?yOunWJZ|3PUdz2T-=n@~e?PKs?Y@eAU-DV`*9r^@$_hRe zG7GQ$VE9A%4=ekl_m32r79B0d6myCv4%i$xSt4GNRWftX>tJW8QfYqa+e4cV4V4*} z)f^@qPCLvi_bl(OP^&1e_*5BNIbLO7)m|-Ey{~%d2~7O*t8J&X7n}m8(oQ}9DdMMz(;lb$&(P1b{jC0TbqBR0zZ2@*+PTmb+r{e+ z?H=uM@9FQg?CtnP|Ch$I%4e(2NuDd}BlP9=t(?y~zjz_}!u-XUi!=S3`yX5ixpZg1 zcVOhQ$K|V6oUdFOv>QBs)#~cmA&a4&YxHYf*G;c?4x0>j+%UP(F=9H>dDHA>_bu~V zy`$?!`^Id>F5Y&$edUhZo$KT4$44guC+<#$Px9`v?moMhaPRf~^!v*Xc1@wD@*j#l zEPEvPsP0$oU)y=6ymQlz)5DMbA3vC3&b;_7<+o3>xlhPX%AP7dZF*+(?ChNL+^y#u zp3lBWez82C_q+J-)i1SP{`|`J)$l^d!t87A>yslKG5F)t<&00*PvxJrKlgs|_%gMUxUwSD0zxey)B-{+Ak+dvEg;kaLM colorSpace = Map.of( + ColorSpace.CS_sRGB, "CS_sRGB", + ColorSpace.CS_PYCC, "CS_PYCC", + ColorSpace.CS_GRAY, "CS_GRAY", + ColorSpace.CS_CIEXYZ, "CS_CIEXYZ", + ColorSpace.CS_LINEAR_RGB, "CS_LINEAR_RGB" + ); public static void main(String[] args) { - test(ICC_Profile.getInstance(ColorSpace.CS_sRGB)); - test(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)); - test(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)); - test(ICC_Profile.getInstance(ColorSpace.CS_PYCC)); - test(ICC_Profile.getInstance(ColorSpace.CS_GRAY)); - } - - private static void test(ICC_Profile profile) { - byte[] tagData = null; - try { - profile.setData(ICC_Profile.icSigCmykData, tagData); - } catch (IllegalArgumentException e) { - return; + for (int cs : colorSpace.keySet()) { + ICC_Profile builtInProfile = ICC_Profile.getInstance(cs); + ICC_Profile profile = ICC_Profile.getInstance(builtInProfile.getData()); + try { + profile.setData(ICC_Profile.icSigCmykData, null); + throw new RuntimeException("IAE expected, but not thrown for " + + "ColorSpace: " + colorSpace.get(cs)); + } catch (IllegalArgumentException e) { + // IAE expected + } } - throw new RuntimeException("IllegalArgumentException expected"); } } diff --git a/test/jdk/sun/java2d/cmm/ProfileOp/SetDataTest.java b/test/jdk/sun/java2d/cmm/ProfileOp/SetDataTest.java index dc86de98bfa..6850061b225 100644 --- a/test/jdk/sun/java2d/cmm/ProfileOp/SetDataTest.java +++ b/test/jdk/sun/java2d/cmm/ProfileOp/SetDataTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2025, 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,7 +29,6 @@ * @run main SetDataTest */ - import java.util.ArrayList; import java.util.List; import java.awt.color.ICC_Profile; @@ -47,7 +46,8 @@ static class TestCase { static byte[] invalidTRCData; static { - profile = ICC_Profile.getInstance(CS_GRAY); + ICC_Profile builtInProfile = ICC_Profile.getInstance(CS_GRAY); + profile = ICC_Profile.getInstance(builtInProfile.getData()); validTRCdata = profile.getData(icSigGrayTRCTag); invalidTRCData = new byte[]{0x42, 0x42, 0x42, 0x42, 1, 3, 4, 6,}; }