You are viewing a plain text version of this content. The canonical link for it is here.
Posted to jdo-commits@db.apache.org by ti...@apache.org on 2026/09/12 16:20:22 UTC
(db-jdo) 01/01: classpath oracle
This is an automated email from the ASF dual-hosted git repository.
tzaeschke pushed a commit to branch classpath-oracle
in repository https://gitbox.apache.org/repos/asf/db-jdo.git
commit 3eb960b39d9e5b5e0747f144085a38369ec3df47
Author: Tilmann <zo...@gmx.de>
AuthorDate: Sat Sep 12 18:20:07 2026 +0200
classpath oracle
---
.../java/javax/jdo/identity/ObjectIdentity.java | 23 +++++++++-
api/src/main/java/javax/jdo/spi/JDOImplHelper.java | 28 ++++++++++++-
api/src/main/resources/javax/jdo/Bundle.properties | 1 -
.../javax/jdo/identity/ObjectIdentityTest.java | 49 ++++++++++++++++++++++
4 files changed, 97 insertions(+), 4 deletions(-)
diff --git a/api/src/main/java/javax/jdo/identity/ObjectIdentity.java b/api/src/main/java/javax/jdo/identity/ObjectIdentity.java
index ca8cd0a8..f82c1150 100644
--- a/api/src/main/java/javax/jdo/identity/ObjectIdentity.java
+++ b/api/src/main/java/javax/jdo/identity/ObjectIdentity.java
@@ -83,7 +83,7 @@ public class ObjectIdentity extends SingleFieldIdentity<ObjectIdentity> {
+ // NOI18N
MSG.msg(
"EXC_ObjectIdentityStringConstructionUsage", // NOI18N
- paramString));
+ sanitized(paramString)));
}
int indexOfDelimiter = paramString.indexOf(STRING_DELIMITER);
if (indexOfDelimiter < 0) {
@@ -92,7 +92,7 @@ public class ObjectIdentity extends SingleFieldIdentity<ObjectIdentity> {
+ // NOI18N
MSG.msg(
"EXC_ObjectIdentityStringConstructionUsage", // NOI18N
- paramString));
+ sanitized(paramString)));
}
keyString = paramString.substring(indexOfDelimiter + 1);
className = paramString.substring(0, indexOfDelimiter);
@@ -106,6 +106,25 @@ public class ObjectIdentity extends SingleFieldIdentity<ObjectIdentity> {
/** Constructor only for Externalizable. */
public ObjectIdentity() {}
+ /**
+ * Replace ISO control characters (e.g. CR/LF) in untrusted text that is echoed into exception
+ * messages, so crafted input cannot forge additional log lines when the message is logged.
+ *
+ * @param text the untrusted text, possibly null
+ * @return the text with control characters replaced by spaces
+ */
+ private static String sanitized(String text) {
+ if (text == null) {
+ return null;
+ }
+ StringBuilder buf = new StringBuilder(text.length());
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ buf.append(Character.isISOControl(c) ? ' ' : c);
+ }
+ return buf.toString();
+ }
+
/**
* Return the key.
*
diff --git a/api/src/main/java/javax/jdo/spi/JDOImplHelper.java b/api/src/main/java/javax/jdo/spi/JDOImplHelper.java
index 10fd95c4..e87c172a 100644
--- a/api/src/main/java/javax/jdo/spi/JDOImplHelper.java
+++ b/api/src/main/java/javax/jdo/spi/JDOImplHelper.java
@@ -845,14 +845,40 @@ public class JDOImplHelper extends java.lang.Object {
InstantiationException,
IllegalAccessException,
InvocationTargetException */
+ /* Deliberately do not embed ex.toString() in the message: the failure
+ kind (ClassNotFoundException vs. NoSuchMethodException vs. constructor
+ failure) would let a caller probing with attacker-controlled identity
+ strings fingerprint which classes exist on the classpath. The original
+ exception stays attached as the nested exception for diagnostics.
+ Control characters are stripped from the echoed input so crafted
+ identity strings cannot forge log lines. */
throw new JDOUserException(
msg.msg(
"EXC_ObjectIdentityStringConstruction", // NOI18N
- new Object[] {ex.toString(), className, keyString}),
+ new Object[] {"", sanitized(className), sanitized(keyString)}),
ex);
}
}
+ /**
+ * Replace ISO control characters (e.g. CR/LF) in untrusted text that is echoed into exception
+ * messages, so crafted input cannot forge additional log lines when the message is logged.
+ *
+ * @param text the untrusted text, possibly null
+ * @return the text with control characters replaced by spaces
+ */
+ private static String sanitized(String text) {
+ if (text == null) {
+ return null;
+ }
+ StringBuilder buf = new StringBuilder(text.length());
+ for (int i = 0; i < text.length(); i++) {
+ char c = text.charAt(i);
+ buf.append(Character.isISOControl(c) ? ' ' : c);
+ }
+ return buf.toString();
+ }
+
/**
* Get the DateFormat instance for the default locale from the VM. This requires the following
* privileges for JDOImplHelper in the security permissions file: permission
diff --git a/api/src/main/resources/javax/jdo/Bundle.properties b/api/src/main/resources/javax/jdo/Bundle.properties
index 208ef456..6c7ade8f 100644
--- a/api/src/main/resources/javax/jdo/Bundle.properties
+++ b/api/src/main/resources/javax/jdo/Bundle.properties
@@ -66,7 +66,6 @@ EXC_StringWrongLength: There must be exactly one character in the id in the inpu
EXC_IllegalEventType:The event type is outside the range of valid event types.
EXC_SingleFieldIdentityNullParameter: The identity must not be null.
EXC_ObjectIdentityStringConstruction: The identity instance could not be constructed. \
-\nThe exception thrown was: "{0}". \
\nParsed the class name as "{1}" and key as "{2}".
EXC_ObjectIdentityStringConstructionNoDelimiter: Missing delimiter ":".
EXC_ObjectIdentityStringConstructionTooShort: Parameter is too short.
diff --git a/api/src/test/java/javax/jdo/identity/ObjectIdentityTest.java b/api/src/test/java/javax/jdo/identity/ObjectIdentityTest.java
index 05e7bae0..a1757bfe 100644
--- a/api/src/test/java/javax/jdo/identity/ObjectIdentityTest.java
+++ b/api/src/test/java/javax/jdo/identity/ObjectIdentityTest.java
@@ -221,6 +221,55 @@ class ObjectIdentityTest extends SingleFieldIdentityTest {
validateNestedException(ex, NoSuchMethodException.class);
}
+ @Test
+ void testBadStringConstructorMessageUniformAcrossFailureKinds() {
+ // The construct-failure message must not reveal whether the named class
+ // exists on the classpath: ClassNotFoundException and NoSuchMethodException
+ // must produce the same text (modulo the echoed class name). The original
+ // exception remains available as the nested exception.
+ JDOUserException notFound =
+ Assertions.assertThrows(
+ JDOUserException.class,
+ () -> new ObjectIdentity(Object.class, "no.such.Clazz:yy"),
+ "Failed to catch expected exception.");
+ JDOUserException noCtor =
+ Assertions.assertThrows(
+ JDOUserException.class,
+ () ->
+ new ObjectIdentity(
+ Object.class,
+ "javax.jdo.identity.ObjectIdentityTest$BadIdClassNoStringConstructor:yy"),
+ "Failed to catch expected exception.");
+ String normalizedNotFound = notFound.getMessage().replace("no.such.Clazz", "CLASS");
+ String normalizedNoCtor =
+ noCtor
+ .getMessage()
+ .replace(
+ "javax.jdo.identity.ObjectIdentityTest$BadIdClassNoStringConstructor", "CLASS");
+ Assertions.assertEquals(
+ normalizedNotFound,
+ normalizedNoCtor,
+ "Exception message must not distinguish failure kinds (classpath oracle).");
+ Assertions.assertFalse(
+ notFound.getMessage().contains("ClassNotFoundException"),
+ "Exception message must not embed the underlying exception type.");
+ }
+
+ @Test
+ void testBadStringConstructorControlCharactersNotEchoed() {
+ JDOUserException ex =
+ Assertions.assertThrows(
+ JDOUserException.class,
+ () -> new ObjectIdentity(Object.class, "no.such.Clazz:evil\nFORGED LOG LINE"),
+ "Failed to catch expected exception.");
+ Assertions.assertFalse(
+ ex.getMessage().contains("evil\nFORGED"),
+ "Control characters from the identity string must not be echoed verbatim.");
+ Assertions.assertTrue(
+ ex.getMessage().contains("evil FORGED"),
+ "Sanitized identity string should still be echoed for diagnostics.");
+ }
+
@Test
void testBadStringConstructorIllegalArgument() {
JDOUserException ex =