From 65271855321b8c3d734ee29f1a955b63c2c2ceee Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Thu, 20 Feb 2025 21:23:34 -0500 Subject: [PATCH 01/12] java.security.manager=disallow to run unenforced JEP 486 finally disallows any means of installing a security manager, as of Java 24, by making 'disallow' the only allowable property value for java.security.manager (other than unset, which is the default and means the same thing, but is distinguishable). Therefore, PL/Java can require the admin to set this explicitly to 'disallow' to indicate "I want to use Java 24+ and, yes, I understand there will be no policy enforcement of any kind." Revise the error / warning / notice messages about the change, now that it's no longer unclear just what form the disabling of functionality will take. Avoid exposing sensitive values such as datadir, libdir, codesource as properties in the no-enforcement mode, as nothing will protect those properties anymore from being read or changed. --- CI/integration | 6 +- pljava-so/src/main/c/Backend.c | 33 +++-- .../postgresql/pljava/internal/Backend.java | 5 +- .../pljava/internal/InstallHelper.java | 113 ++++++++++-------- 4 files changed, 95 insertions(+), 62 deletions(-) diff --git a/CI/integration b/CI/integration index 9b0c1e066..3af66181a 100644 --- a/CI/integration +++ b/CI/integration @@ -131,8 +131,10 @@ int jFeatureVersion = Runtime.version().major(); String vmopts = "-enableassertions:org.postgresql.pljava... -Xcheck:jni"; -if ( 17 < jFeatureVersion ) - vmopts += " -Djava.security.manager=allow"; +if ( 24 <= jFeatureVersion ) { + vmopts += " -Djava.security.manager=disallow"; // JEP 486 +} else if ( 18 <= jFeatureVersion ) + vmopts += " -Djava.security.manager=allow"; // JEP 411 if ( 23 <= jFeatureVersion ) vmopts += " --sun-misc-unsafe-memory-access=deny"; // JEP 471 diff --git a/pljava-so/src/main/c/Backend.c b/pljava-so/src/main/c/Backend.c index e4324babb..3070f2134 100644 --- a/pljava-so/src/main/c/Backend.c +++ b/pljava-so/src/main/c/Backend.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2024 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -232,6 +232,15 @@ static bool deferInit = false; */ static bool warnJEP411 = true; +/* + * Becomes true upon initialization of the Backend class if the Java property + * setting java.security.manager=disallow was explicitly in pljava.vmoptions. + * That is how to request the fallback nothing-is-enforced mode of operation + * that is the only mode available on Java >= 24. Only when all Java code is + * 100% trusted should PL/Java be run in this mode. + */ +static bool withoutEnforcement = false; + /* * Don't bother with the warning unless the JVM in use is later than Java 11. * 11 is the LTS release prior to the one where JEP 411 gets interesting (17). @@ -1012,6 +1021,10 @@ static void initPLJavaClasses(void) javaGT11 = 11 < javaMajor; javaGE17 = 17 <= javaMajor; + fID = PgObject_getStaticJavaField(s_Backend_class,\ + "WITHOUT_ENFORCEMENT", "Z"); + withoutEnforcement = JNI_getStaticBooleanField(s_Backend_class, fID); + fID = PgObject_getStaticJavaField(s_Backend_class, "THREADLOCK", "Ljava/lang/Object;"); JNI_setThreadLock(JNI_getStaticObjectField(s_Backend_class, fID)); @@ -1920,7 +1933,7 @@ void Backend_warnJEP411(bool isCommit) { static bool warningEmitted = false; /* once only per session */ - if ( warningEmitted || ! warnJEP411 ) + if ( ! warnJEP411 || withoutEnforcement || warningEmitted ) return; if ( ! isCommit ) @@ -1933,17 +1946,23 @@ void Backend_warnJEP411(bool isCommit) ereport(javaGE17 ? WARNING : NOTICE, ( errmsg( - "[JEP 411] migration advisory: there will be a Java version " - "(after Java 17) that will be unable to run PL/Java %s " - "with policy enforcement", SO_VERSION_STRING), + "[JEP 411] migration advisory: Java version 24 and later " + "cannot run PL/Java %s with policy enforcement", SO_VERSION_STRING), errdetail( "This PL/Java version enforces security policy using important " - "Java features that will be phased out in future Java versions. " - "Those changes will come in releases after Java 17."), + "Java features that upstream Java has disabled as of Java 24, " + "as described in JEP 486. In Java 18 through 23, enforcement is " + "still available, but requires " + "\"-Djava.security.manager=allow\" in \"pljava.vmoptions\". "), errhint( "For migration planning, this version of PL/Java can still " "enforce policy in Java versions up to and including 23, " "and Java 17 and 21 are positioned as long-term support releases. " + "Java 24 and later can be used, if wanted, WITH ABSOLUTELY NO " + "EXPECTATIONS OF SECURITY, by adding " + "\"-Djava.security.manager=disallow\" in \"pljava.vmoptions\". " + "This mode should be considered only if all Java code to be used " + "is considered completely vetted and trusted. " "For details on how PL/Java will adapt, please bookmark " "https://github.com/tada/pljava/wiki/JEP-411") )); diff --git a/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java b/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java index 47bfe9051..0340caf53 100644 --- a/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java +++ b/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2022 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -47,6 +47,9 @@ public class Backend */ public static final ThreadLocal IAMPGTHREAD = new ThreadLocal<>(); + public static final boolean WITHOUT_ENFORCEMENT = + "disallow".equals(System.getProperty("java.security.manager")); + static final int JAVA_MAJOR = Runtime.version().major(); static diff --git a/pljava/src/main/java/org/postgresql/pljava/internal/InstallHelper.java b/pljava/src/main/java/org/postgresql/pljava/internal/InstallHelper.java index d9bd12a85..0e3f7f585 100644 --- a/pljava/src/main/java/org/postgresql/pljava/internal/InstallHelper.java +++ b/pljava/src/main/java/org/postgresql/pljava/internal/InstallHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2024 Tada AB and other contributors, as listed below. + * Copyright (c) 2015-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -43,6 +43,7 @@ import org.postgresql.pljava.policy.TrialPolicy; import static org.postgresql.pljava.annotation.processing.DDRWriter.eQuote; import static org.postgresql.pljava.elog.ELogHandler.LOG_WARNING; +import static org.postgresql.pljava.internal.Backend.WITHOUT_ENFORCEMENT; import static org.postgresql.pljava.sqlgen.Lexicals.Identifier.Simple; /** @@ -109,10 +110,15 @@ public static String hello( setPropertyIfNull( "org.postgresql.database", dbname); if ( null != clustername ) setPropertyIfNull( "org.postgresql.cluster", clustername); - setPropertyIfNull( "org.postgresql.datadir", datadir); - setPropertyIfNull( "org.postgresql.libdir", libdir); - setPropertyIfNull( "org.postgresql.sharedir", sharedir); - setPropertyIfNull( "org.postgresql.sysconfdir", etcdir); + + if ( ! WITHOUT_ENFORCEMENT ) + { + setPropertyIfNull( "org.postgresql.datadir", datadir); + setPropertyIfNull( "org.postgresql.libdir", libdir); + setPropertyIfNull( "org.postgresql.sharedir", sharedir); + setPropertyIfNull( "org.postgresql.sysconfdir", etcdir); + } + setPropertyIfNull( "org.postgresql.pljava.version", implVersion); setPropertyIfNull( "org.postgresql.pljava.native.version", nativeVer); setPropertyIfNull( "org.postgresql.version", @@ -165,11 +171,14 @@ public static String hello( } /* so it can be granted permissions in the pljava policy */ - System.setProperty( "org.postgresql.pljava.codesource", - InstallHelper.class.getProtectionDomain().getCodeSource() - .getLocation().toString()); + if ( ! WITHOUT_ENFORCEMENT ) + { + System.setProperty( "org.postgresql.pljava.codesource", + InstallHelper.class.getProtectionDomain().getCodeSource() + .getLocation().toString()); - setPolicyURLs(); + setPolicyURLs(); + } /* * Construct the strings announcing the versions in use. @@ -187,18 +196,21 @@ public static String hello( String vmVer = System.getProperty( "java.vm.version"); String vmInfo = System.getProperty( "java.vm.info"); - try + if ( ! WITHOUT_ENFORCEMENT ) { - new URL("sqlj:x"); // sqlj: scheme must exist before reading policy - } - catch ( MalformedURLException e ) - { - throw new SecurityException( + try + { + new URL("sqlj:x"); // sqlj scheme must exist when reading policy + } + catch ( MalformedURLException e ) + { + throw new SecurityException( "failed to create sqlj: URL scheme needed for security policy", - e); - } + e); + } - beginEnforcing(); + beginEnforcing(); + } StringBuilder sb = new StringBuilder(); sb.append( "PL/Java native code (").append( nativeVer).append( ")\n"); @@ -274,9 +286,14 @@ private static void setPolicyURLs() { prevURL = Security.getProperty( "policy.url." + prevIndex); if ( null == prevURL ) + { + boolean hint = + (2 == urlIndex) && 24 <= Runtime.version().major(); + throw new SQLNonTransientException(String.format( - "URL at %d in pljava.policy_urls follows an unset URL", - urlIndex), "F0000"); + "URL at %d in pljava.policy_urls follows an unset URL" + + (hint ? (". " + jepSuffix) : ""), urlIndex), "F0000"); + } } if ( -1 != stopIndex ) continue; /* should be last, but resume loop to make sure */ @@ -304,25 +321,23 @@ private static void setPolicyURLs() * layer-inappropriate boilerplate warning message when running on Java 17 * or later, and react if the operation has been disallowed or "degraded". *

- * If {@code getSecurityManager} still returns null after being set, and - * the Java major version is greater than 17, this can be a sign of - * "degradation" of the security API proposed in JEP 411. It may be ignored - * by setting {@code -Dorg.postgresql.pljava.policy.enforcement=none} in - * {@code pljava.vmoptions}. That may permit PL/Java to run, but - * without enforcing any policy at all, no distinction between trusted and - * untrusted functions, and so on. However, given uncertainty around exactly - * how the Java developers will "degrade" the API in a given Java release, - * the result may simply be a different failure of PL/Java to start or - * properly function. + * The expected form of "degradation" as of Java 24 with JEP 486 is for + * {@code setSecurityManager} to throw + * {@code UnsupportedOperationException}. Nonetheless, we still also check + * that {@code getSecurityManager} returns the instance we intended to set. + *

+ * JEP 486 explicitly allows the property {@code java.security.manager} to + * be set to {@code disallow} at invocation, and this detectably differs + * from its null default (despite the semantic equivalence), so that will be + * the setting to include in {@code pljava.vmoptions} to indicate that + * running without any policy enforcement is ok. When that property is so + * set, this method is not even called. */ private static void beginEnforcing() throws SQLException { String trialURI = System.getProperty( "org.postgresql.pljava.policy.trial"); - String enforcement = System.getProperty( - "org.postgresql.pljava.policy.enforcement"); - if ( null != trialURI ) { try @@ -349,43 +364,37 @@ private static void beginEnforcing() throws SQLException } catch ( UnsupportedOperationException e ) { - if ( 17 >= major ) + if ( 24 >= major ) throw new SQLException( "Unexpected failure enabling permission enforcement", e); throw new SQLNonTransientException( "[JEP 411] The Java version selected, " + Runtime.version() + ", has not allowed PL/Java to enforce security policy. " + - "It may help to add -Djava.security.manager=allow in " + - "the pljava.vmoptions setting. However, that may require " + - "allowing PL/Java functions to execute with no policy " + - "enforcement, or simply lead to a different failure " + - "to start. If that is unacceptable, " + jepSuffix, "58000", e); + ( 24 > major ? allowHint : "" ) + jepSuffix, "58000", e); } - if ( 17 >= major ) - throw new SQLException( - "Unexpected failure enabling permission enforcement"); - - if ( "none".equals(enforcement) ) - return; - throw new SQLNonTransientException( "[JEP 411] The Java version selected, " + Runtime.version() + ", cannot enforce security policy as this PL/Java version " + - "requires. To allow PL/Java to run with no enforcement of " + - "security (for example, trusted functions as untrusted), add " + - "-Dorg.postgresql.pljava.policy.enforcement=none in the " + - "pljava.vmoptions setting. However, this may lead only to a " + - "different failure to start. In that case, " + - jepSuffix, "58000"); + "requires. " + ( 24 > major ? allowHint : "" ) + jepSuffix, + "58000"); } private static final String jepSuffix = + "With Java 24 and later, this version of PL/Java can only operate " + + "with -Djava.security.manager=disallow set in pljava.vmoptions, " + + "resulting in no enforcement of any security expectations, no " + + "distinction between trusted and untrusted, and so on. If that is " + + "unacceptable, " + "pljava.libjvm_location should be pointed to an earlier version " + "of Java, or a newer PL/Java version should be used. For more " + "explanation, please see " + "https://github.com/tada/pljava/wiki/JEP-411"; + private static final String allowHint = + "To enforce security policy in Java 18 through 23, the setting " + + "-Djava.security.manager=allow must be added in pljava.vmoptions. "; + /** * When PL/Java is loaded as an end-in-itself (that is, by {@code LOAD} * on its own or from its extension script on {@code CREATE EXTENSION} or From a4ce7fcfbc32c9894f0bb9f540445ed9ecbaffe4 Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Thu, 20 Feb 2025 21:23:45 -0500 Subject: [PATCH 02/12] Restrict trusted CREATE FUNCTION when unenforced In the no-enforcement mode selected with java.security.manager=disallow, enforce (in the validator) that function creation even in a PL flagged 'trusted' is reserved to superusers. This check is made unconditionally, right after CheckFunctionValidatorAccess but before the short-circuit return made if that returns false (a reserved possible future case), and before consulting check_function_bodies. Naturally, any functions already defined in 'trusted' PLs, prior to a PL/Java and/or Java update newly configured for no enforcement, remain defined. Selectively enabling those to be executable in no-enforcement mode will be the subject of the next patch. --- pljava-so/src/main/c/Backend.c | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/pljava-so/src/main/c/Backend.c b/pljava-so/src/main/c/Backend.c index 3070f2134..235e36b7e 100644 --- a/pljava-so/src/main/c/Backend.c +++ b/pljava-so/src/main/c/Backend.c @@ -1850,7 +1850,32 @@ static Datum internalValidator(bool trusted, PG_FUNCTION_ARGS) Invocation ctx; Oid *oidSaveLocation = NULL; - if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) + bool ok = CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid); + /* + * CheckFunctionValidatorAccess reserves a possible future behavior where + * it returns false and this validator should immediately return. Here we + * abuse that convention slightly by first checking an additional constraint + * on function creation in withoutEnforcing mode. That, arguably, is a check + * that should never be skipped, just like the permission checks made in + * CheckFunctionValidatorAccess itself. + */ + if ( withoutEnforcement && trusted && ! superuser() ) + ereport(ERROR, ( + errmsg( + "trusted PL/Java language restricted to superuser when " + "\"java.security.manager\"=\"disallow\""), + errdetail( + "This PL/Java version enforces security policy using important " + "Java features that upstream Java has disabled as of Java 24, " + "as described in JEP 486. In Java 18 through 23, enforcement is " + "still available, but requires " + "\"-Djava.security.manager=allow\" in \"pljava.vmoptions\". " + "The alternative \"-Djava.security.manager=disallow\" permits " + "use on Java 24 and later, but with no enforcement and no " + "distinction between trusted and untrusted. In this mode, only " + "a superuser may use even a 'trusted' PL/Java language") + )); + if ( ! ok ) PG_RETURN_VOID(); /* From 94dbf3cd8b45ebb17359251e69d42f534b1ca883 Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Thu, 20 Feb 2025 21:23:56 -0500 Subject: [PATCH 03/12] Add pljava.allow_unenforced configuration setting The value is a comma-separated list of PL names (such as java, javau, possibly others created with sqlj.alias_java_language). Only functions defined in the PLs named here will be allowed to execute when the java.security.manager=disallow property setting is in effect. This will also be checked in the validator, thus preventing creation of a new function if the PL is not named here. This is checked in the validator only when check_function_bodies is on, so it is better viewed as a reminder than as meaningful security. The check at function invocation is the important one. --- CI/integration | 14 +++--- pljava-so/src/main/c/Backend.c | 43 +++++++++++++++++-- .../postgresql/pljava/internal/Backend.java | 6 ++- .../postgresql/pljava/internal/Function.java | 31 +++++++++++-- 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/CI/integration b/CI/integration index 3af66181a..6643ecc46 100644 --- a/CI/integration +++ b/CI/integration @@ -142,6 +142,14 @@ if ( 23 <= jFeatureVersion ) if ( 24 <= jFeatureVersion ) vmopts += " --illegal-native-access=deny"; // JEP 472 +Map serverOptions = new HashMap<>(Map.of( + "client_min_messages", "info", + "pljava.vmoptions", vmopts, + "pljava.libjvm_location", libjvm.toString() +)); +if ( 24 <= jFeatureVersion ) + serverOptions.put("pljava.allow_unenforced", "java,java_tzset"); + Node n1 = Node.get_new_node("TestNode1"); if ( s_isWindows ) @@ -210,11 +218,7 @@ throws Exception try ( AutoCloseable t1 = n1.initialized_cluster(tweaks); - AutoCloseable t2 = n1.started_server(Map.of( - "client_min_messages", "info", - "pljava.vmoptions", vmopts, - "pljava.libjvm_location", libjvm.toString() - ), tweaks); + AutoCloseable t2 = n1.started_server(serverOptions, tweaks); ) { int pgMajorVersion; diff --git a/pljava-so/src/main/c/Backend.c b/pljava-so/src/main/c/Backend.c index 235e36b7e..8f1e1ff57 100644 --- a/pljava-so/src/main/c/Backend.c +++ b/pljava-so/src/main/c/Backend.c @@ -115,6 +115,7 @@ static char* vmoptions; static char* modulepath; static char* implementors; static char* policy_urls; +static char* allow_unenforced; static int statementCacheSize; static bool pljavaDebug; static bool pljavaReleaseLingeringSavepoints; @@ -207,6 +208,7 @@ static bool seenModuleMain; static char const visualVMprefix[] = "-Dvisualvm.display.name="; static char const moduleMainPrefix[] = "-Djdk.module.main="; static char const policyUrlsGUC[] = "pljava.policy_urls"; +static char const unenforcedGUC[] = "pljava.allow_unenforced"; /* * In a background worker, _PG_init may be called very early, before much of @@ -460,6 +462,15 @@ ASSIGNSTRINGHOOK(policy_urls) ASSIGNRETURN(newval); } +ASSIGNSTRINGHOOK(allow_unenforced) +{ + ASSIGNRETURNIFCHECK(newval); + allow_unenforced = (char *)newval; + if ( IS_PLJAVA_FOUND < initstage ) + Function_clearFunctionCache(); + ASSIGNRETURN(newval); +} + ASSIGNHOOK(enabled, bool) { ASSIGNRETURNIFCHECK(true); @@ -1672,6 +1683,21 @@ static void registerGUCOptions(void) assign_policy_urls, NULL); /* show hook */ + STRING_GUC( + unenforcedGUC, + "Which PL/Java-based PLs may execute without security enforcement", + "List the language names (such as javau) separated by commas. When " + "PL/Java is loaded with -Djava.security.manager=disallow (as is " + "needed on Java 24 and later), only functions in the languages named " + "here can be executed.", + &allow_unenforced, + NULL, /* boot value */ + PGC_SUSET, + PLJAVA_IMPLEMENTOR_FLAGS | GUC_SUPERUSER_ONLY, + NULL, /* check hook */ + assign_allow_unenforced, + NULL); /* show hook */ + BOOL_GUC( "pljava.debug", "Stop the backend to attach a debugger", @@ -2018,10 +2044,21 @@ JNICALL Java_org_postgresql_pljava_internal_Backend__1getConfigOption(JNIEnv* en PG_TRY(); { const char *value; - if ( 0 == strcmp(policyUrlsGUC, key) ) + if ( 0 != strncmp(policyUrlsGUC, key, 7) ) + goto fallback; + if ( 0 == strcmp(policyUrlsGUC+7, key+7) ) + { value = policy_urls; - else - value = PG_GETCONFIGOPTION(key); + goto finish; + } + if ( 0 == strcmp(unenforcedGUC+7, key+7) ) + { + value = allow_unenforced; + goto finish; + } +fallback: + value = PG_GETCONFIGOPTION(key); +finish: pfree(key); if(value != 0) result = String_createJavaStringFromNTS(value); diff --git a/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java b/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java index 0340caf53..0a923ac91 100644 --- a/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java +++ b/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java @@ -246,7 +246,11 @@ public static String getConfigOption(String key) public static List getListConfigOption(String key) throws SQLException { - final Matcher m = s_gucList.matcher(getConfigOption(key)); + String s = getConfigOption(key); + if ( null == s ) + return null; + + final Matcher m = s_gucList.matcher(s); ArrayList al = new ArrayList<>(); while ( m.find() ) { diff --git a/pljava/src/main/java/org/postgresql/pljava/internal/Function.java b/pljava/src/main/java/org/postgresql/pljava/internal/Function.java index 6015bde7d..05e9bfea3 100644 --- a/pljava/src/main/java/org/postgresql/pljava/internal/Function.java +++ b/pljava/src/main/java/org/postgresql/pljava/internal/Function.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2023 Tada AB and other contributors, as listed below. + * Copyright (c) 2016-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -68,6 +68,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -82,6 +83,8 @@ import org.postgresql.pljava.sqlgen.Lexicals.Identifier; import static org.postgresql.pljava.internal.Backend.doInPG; +import static org.postgresql.pljava.internal.Backend.getListConfigOption; +import static org.postgresql.pljava.internal.Backend.WITHOUT_ENFORCEMENT; import org.postgresql.pljava.internal.EntryPoints; import org.postgresql.pljava.internal.EntryPoints.Invocable; import static org.postgresql.pljava.internal.EntryPoints.invocable; @@ -1507,17 +1510,37 @@ private static boolean isTrigger(ResultSet procTup) * conditions. No exception is made here for the few functions supplied by * PL/Java's own {@code Commands} class; they get a lid. It is reasonable to * ask them to use {@code doPrivileged} when appropriate. + *

+ * When {@code WITHOUT_ENFORCEMENT} is true, any nonnull language + * must be named in {@code pljava.allow_unenforced}. PL/Java's own functions + * in the {@code Commands} class are exempt from that check. */ private static AccessControlContext accessControlContextFor( Class clazz, String language, boolean trusted) + throws SQLException { + Identifier.Simple langIdent = null; + if ( null != language ) + langIdent = Identifier.Simple.fromCatalog(language); + + if ( WITHOUT_ENFORCEMENT && null != langIdent + && clazz != Commands.class ) + { + if ( Optional.ofNullable( + getListConfigOption("pljava.allow_unenforced") + ).orElseGet(List::of).stream().noneMatch(langIdent::equals) ) + throw new SQLNonTransientException( + "PL \"" + language + "\" not listed in " + + "pljava.allow_unenforced configuration setting", "46000"); + } + Set p = - (null == language) + (null == langIdent) ? Set.of() : Set.of( trusted - ? new PLPrincipal.Sandboxed(language) - : new PLPrincipal.Unsandboxed(language) + ? new PLPrincipal.Sandboxed(langIdent) + : new PLPrincipal.Unsandboxed(langIdent) ); AccessControlContext acc = clazz.getClassLoader() instanceof Loader From acdb9d73f995ff927f1a4603210ae3edf704ec43 Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Thu, 20 Feb 2025 21:24:02 -0500 Subject: [PATCH 04/12] Hook JVM abort and exit The JVM has long has hooks for both, just as sparsely documented as the vfprintf hook, which PL/Java has already long used. This is a good time to start hooking abort and exit also, in part because of the seemingly ever-growing set of JVM startup issues that get reported with an abort instead of a civilized error return from JNI_CreateJavaVM, and in part because if there's to be no policy enforcement around System.exit(), one likes at least to know what has happened if it gets called. (Regrettably, the exit hook is not given the option to just say "no".) --- pljava-so/src/main/c/Backend.c | 99 +++++++++++++++++----- pljava-so/src/main/include/pljava/pljava.h | 8 +- 2 files changed, 83 insertions(+), 24 deletions(-) diff --git a/pljava-so/src/main/c/Backend.c b/pljava-so/src/main/c/Backend.c index 8f1e1ff57..832b69b5b 100644 --- a/pljava-so/src/main/c/Backend.c +++ b/pljava-so/src/main/c/Backend.c @@ -148,6 +148,23 @@ extern void SQLOutputToChunk_initialize(void); extern void SQLOutputToTuple_initialize(void); +/* + * These typedefs are not exposed in Java's jni.h. Apparently you are supposed + * to be really determined if you want to use them. These are copy/pasted from + * src/hotspot/share/runtime/arguments.hpp. One silver lining is that they can + * be spelled here without the * used in the original, enabling them to be used + * succinctly to declare matching prototypes. + */ +typedef void JNICALL abort_hook_t(void); +typedef void JNICALL exit_hook_t(jint code); +typedef jint JNICALL vfprintf_hook_t(FILE *fp, const char *fmt, va_list args) + pg_attribute_printf(2, 0); + +/* + * This private type is used here as a dynamically-sized list of JavaVMOption, + * which will later be copied to a struct of type JavaVMInitArgs (a type that + * jni.h does expose). + */ typedef struct { JavaVMOption* options; unsigned int size; @@ -163,8 +180,9 @@ static void JVMOptList_addVisualVMName(JVMOptList*); static void JVMOptList_addModuleMain(JVMOptList*); static void addUserJVMOptions(JVMOptList*); static char* getModulePath(const char*); -static jint JNICALL my_vfprintf(FILE*, const char*, va_list) - pg_attribute_printf(2, 0); +static abort_hook_t my_abort; +static exit_hook_t my_exit; +static vfprintf_hook_t my_vfprintf; static void _destroyJavaVM(int, Datum); static void initPLJavaClasses(void); static void initJavaSession(void); @@ -655,6 +673,8 @@ static void initsequencer(enum initstage is, bool tolerant) JVMOptList_addVisualVMName(&optList); if ( ! seenModuleMain ) JVMOptList_addModuleMain(&optList); + JVMOptList_add(&optList, "abort", (void*)my_abort, true); + JVMOptList_add(&optList, "exit", (void*)my_exit, true); JVMOptList_add(&optList, "vfprintf", (void*)my_vfprintf, true); #ifndef GCJ JVMOptList_add(&optList, "-Xrs", 0, true); @@ -1063,7 +1083,60 @@ int Backend_setJavaLogLevel(int logLevel) s_javaLogLevel = logLevel; return oldLevel; } - + +static const char DEATH_HINT[] = + "Depending on log_min_messages and whether logging_collector is active, " + "relevant information may be near this message in the server log. If " + "during VM startup, pljava.vmoptions and other pljava.* settings should " + "be checked for mistakes or incompatibility with the Java version of the " + "library pljava.libjvm_location points to. Causes can include a misspelled " + "entry in pljava.module_path or a jar that can't be opened on that path. " + "If during \"CREATE EXTENSION pljava\" and there is little information in " + "the log, try in a new session with LOAD rather than CREATE EXTENSION."; + +static void onJVMExitOrAbort(void); + +static void JNICALL my_abort() +{ + onJVMExitOrAbort(); + ereport(FATAL, ( + errcode(ERRCODE_CLASS_SQLJRT), + errmsg("PostgreSQL backend exiting because Java VM requested abort"), + errdetail("Abort requested %s.", + s_startingVM ? "during VM startup" : "by already started VM"), + errhint(DEATH_HINT) + )); +} + +static void JNICALL my_exit(jint code) +{ + onJVMExitOrAbort(); + ereport(FATAL, ( + errcode(ERRCODE_CLASS_SQLJRT), + errmsg("PostgreSQL backend exiting because Java VM requested exit " + "with code %d", (int)code), + errdetail("Exit requested %s.", + s_startingVM ? "during VM startup" : "by already started VM"), + errhint(DEATH_HINT) + )); +} + +static void onJVMExitOrAbort() +{ + /* + * We will later hit the proc_exit handler, which will try to destroy the + * already-gone JVM if this reference is non-null. + */ + s_javaVM = NULL; + /* + * This does a PostgreSQL UnregisterResourceReleaseCallback, which should + * be painless if the callback hasn't been registered yet. The key is to + * avoid triggering a DualState callback that tries a JNI upcall into + * the already-gone JVM. + */ + pljava_DualState_unregister(); +} + /** * Special purpose logging function called from JNI when verbose is enabled. */ @@ -1318,25 +1391,7 @@ static void terminationTimeoutHandler() */ static void _destroyJavaVM(int status, Datum dummy) { - if(s_javaVM == 0) - { - if ( s_startingVM ) - { - ereport(FATAL, ( - errcode(ERRCODE_INTERNAL_ERROR), - errmsg("the Java VM exited while loading PL/Java"), - errdetail( - "The Java VM's exit forces this session to end."), - errhint( - "This has been known to happen when the entry in " - "pljava.module_path for the pljava-api jar has been " - "misspelled or the jar cannot be opened. If " - "logging_collector is active, there may be useful " - "information in the log.") - )); - } - } - else + if(s_javaVM != 0) { Invocation ctx; #ifdef USE_PLJAVA_SIGHANDLERS diff --git a/pljava-so/src/main/include/pljava/pljava.h b/pljava-so/src/main/include/pljava/pljava.h index 6a2e298c9..cdc649fdf 100644 --- a/pljava-so/src/main/include/pljava/pljava.h +++ b/pljava-so/src/main/include/pljava/pljava.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2023 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -146,7 +146,11 @@ extern MemoryContext JavaMemoryContext; * * Class 07 - Dynamic SQL Exception */ -#define ERRCODE_INVALID_DESCRIPTOR_INDEX MAKE_SQLSTATE('0','7', '0','0','9') +#define ERRCODE_INVALID_DESCRIPTOR_INDEX MAKE_SQLSTATE('0','7', '0','0','9') +/* + * Class 46 - SQL/JRT + */ +#define ERRCODE_CLASS_SQLJRT MAKE_SQLSTATE('4','6','0','0','0') /* * Union used when coercing void* to jlong and vice versa From 752ba0199a2cd7f9f99daabf6dd7461bbb1f23ab Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Sun, 23 Feb 2025 09:41:32 -0500 Subject: [PATCH 05/12] Add pljava.allow_unenforced_udt config setting This one is a simple boolean. The Java readSQL/writeSQL methods of a MappedUDT get called by PL/Java directly, not through declared SQL functions with an associated language name, so they slip through the cracks of pljava.allow_unenforced based on language name. No MappedUDT readSQL/writeSQL methods will be executed if this setting is off. They'll be executed normally (to the extent "without enforcement" can be called "normally") when it is on. --- CI/integration | 4 +- pljava-so/src/main/c/Backend.c | 47 +++++++++++++++++++ .../postgresql/pljava/internal/Backend.java | 6 +++ .../postgresql/pljava/internal/Function.java | 20 +++++--- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/CI/integration b/CI/integration index 6643ecc46..06f7b0154 100644 --- a/CI/integration +++ b/CI/integration @@ -147,8 +147,10 @@ Map serverOptions = new HashMap<>(Map.of( "pljava.vmoptions", vmopts, "pljava.libjvm_location", libjvm.toString() )); -if ( 24 <= jFeatureVersion ) +if ( 24 <= jFeatureVersion ) { serverOptions.put("pljava.allow_unenforced", "java,java_tzset"); + serverOptions.put("pljava.allow_unenforced_udt", "on"); +} Node n1 = Node.get_new_node("TestNode1"); diff --git a/pljava-so/src/main/c/Backend.c b/pljava-so/src/main/c/Backend.c index 832b69b5b..2e66d12c7 100644 --- a/pljava-so/src/main/c/Backend.c +++ b/pljava-so/src/main/c/Backend.c @@ -117,6 +117,7 @@ static char* implementors; static char* policy_urls; static char* allow_unenforced; static int statementCacheSize; +static bool allow_unenforced_udt; static bool pljavaDebug; static bool pljavaReleaseLingeringSavepoints; static bool pljavaEnabled; @@ -286,6 +287,8 @@ static bool check_policy_urls( char **newval, void **extra, GucSource source); static bool check_enabled( bool *newval, void **extra, GucSource source); +static bool check_allow_unenforced_udt( + bool *newval, void **extra, GucSource source); static bool check_java_thread_pg_entry( int *newval, void **extra, GucSource source); @@ -387,6 +390,22 @@ static bool check_enabled( return false; } +static bool check_allow_unenforced_udt( + bool *newval, void **extra, GucSource source) +{ + if ( initstage < IS_PLJAVA_FOUND ) + return true; + if ( *newval || ! allow_unenforced_udt ) + return true; + GUC_check_errmsg( + "too late to change \"pljava.allow_unenforced_udt\" setting"); + GUC_check_errdetail( + "Once set, it cannot be reset in the same session."); + GUC_check_errhint( + "For another chance, exit this session and start a new one."); + return false; +} + static bool check_java_thread_pg_entry( int *newval, void **extra, GucSource source) { @@ -1001,6 +1020,11 @@ static void initPLJavaClasses(void) Java_org_postgresql_pljava_internal_Backend__1isCreatingExtension }, { + "_allowingUnenforcedUDT", + "()Z", + Java_org_postgresql_pljava_internal_Backend__1allowingUnenforcedUDT + }, + { "_myLibraryPath", "()Ljava/lang/String;", Java_org_postgresql_pljava_internal_Backend__1myLibraryPath @@ -1802,6 +1826,18 @@ static void registerGUCOptions(void) assign_enabled, NULL); /* show hook */ + BOOL_GUC( + "pljava.allow_unenforced_udt", + "Whether PL/Java-based \"mapped UDT\" data conversion functions are " + "allowed to execute without security enforcement", + NULL, /* extended description */ + &allow_unenforced_udt, + false, /* boot value */ + PGC_SUSET, + GUC_SUPERUSER_ONLY, /* flags */ + check_allow_unenforced_udt, /* check hook */ + NULL, NULL); /* assign hook, show hook */ + STRING_GUC( "pljava.implementors", "Implementor names recognized in deployment descriptors", @@ -2214,6 +2250,17 @@ Java_org_postgresql_pljava_internal_Backend__1isCreatingExtension(JNIEnv *env, j return inExtension ? JNI_TRUE : JNI_FALSE; } +/* + * Class: org_postgresql_pljava_internal_Backend + * Method: _allowingUnenforcedUDT + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL +Java_org_postgresql_pljava_internal_Backend__1allowingUnenforcedUDT(JNIEnv *env, jclass cls) +{ + return allow_unenforced_udt; +} + /* * Class: org_postgresql_pljava_internal_Backend * Method: _myLibraryPath diff --git a/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java b/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java index 0a923ac91..326048437 100644 --- a/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java +++ b/pljava/src/main/java/org/postgresql/pljava/internal/Backend.java @@ -296,6 +296,11 @@ public static boolean isCreatingExtension() return doInPG(Backend::_isCreatingExtension); } + public static boolean allowingUnenforcedUDT() + { + return doInPG(Backend::_allowingUnenforcedUDT); + } + /** * Returns the path of PL/Java's shared library. * @throws SQLException if for some reason it can't be determined. @@ -342,6 +347,7 @@ static void pokeJEP411() private static native boolean _isCreatingExtension(); private static native String _myLibraryPath(); private static native void _pokeJEP411(Class caller, Object token); + private static native boolean _allowingUnenforcedUDT(); private static class EarlyNatives { diff --git a/pljava/src/main/java/org/postgresql/pljava/internal/Function.java b/pljava/src/main/java/org/postgresql/pljava/internal/Function.java index 05e9bfea3..f9b3d346c 100644 --- a/pljava/src/main/java/org/postgresql/pljava/internal/Function.java +++ b/pljava/src/main/java/org/postgresql/pljava/internal/Function.java @@ -85,6 +85,7 @@ import static org.postgresql.pljava.internal.Backend.doInPG; import static org.postgresql.pljava.internal.Backend.getListConfigOption; import static org.postgresql.pljava.internal.Backend.WITHOUT_ENFORCEMENT; +import static org.postgresql.pljava.internal.Backend.allowingUnenforcedUDT; import org.postgresql.pljava.internal.EntryPoints; import org.postgresql.pljava.internal.EntryPoints.Invocable; import static org.postgresql.pljava.internal.EntryPoints.invocable; @@ -774,7 +775,7 @@ private static void push() } /** - * Pop a stacked parameter frame; called only vi JNI, only when + * Pop a stacked parameter frame; called only via JNI, only when * the current invocation is known to have pushed one. */ private static void pop() @@ -1523,15 +1524,22 @@ private static AccessControlContext accessControlContextFor( if ( null != language ) langIdent = Identifier.Simple.fromCatalog(language); - if ( WITHOUT_ENFORCEMENT && null != langIdent - && clazz != Commands.class ) + if ( WITHOUT_ENFORCEMENT && clazz != Commands.class ) { - if ( Optional.ofNullable( + if ( null == langIdent ) + { + if ( ! allowingUnenforcedUDT() ) + throw new SQLNonTransientException( + "PL/Java UDT data conversions for " + clazz + + " cannot execute because pljava.allow_unenforced_udt" + + " is off", "46000"); + } + else if ( Optional.ofNullable( getListConfigOption("pljava.allow_unenforced") ).orElseGet(List::of).stream().noneMatch(langIdent::equals) ) throw new SQLNonTransientException( - "PL \"" + language + "\" not listed in " + - "pljava.allow_unenforced configuration setting", "46000"); + "PL \"" + language + "\" not listed in " + + "pljava.allow_unenforced configuration setting", "46000"); } Set p = From fa82ade57fc10e8698e2a9fd29e3c6c093c5c105 Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Sun, 23 Feb 2025 19:18:58 -0500 Subject: [PATCH 06/12] Document unenforced operation Maybe also phrase the Backend.c JEP 411 warning a little bit less apocalyptically. Having suggested --limit-modules=org.postgresql.pljava.internal and claimed that it doesn't break the tests, add it to the CI script to make sure it stays that way. --- CI/integration | 2 + pljava-so/src/main/c/Backend.c | 4 +- src/site/markdown/install/install.md.vm | 42 ++++- src/site/markdown/install/locatejvm.md | 16 ++ src/site/markdown/install/vmoptions.md | 26 +++ src/site/markdown/use/jpms.md | 32 ++++ src/site/markdown/use/policy.md | 23 ++- src/site/markdown/use/unenforced.md | 214 ++++++++++++++++++++++++ src/site/markdown/use/use.md | 11 +- src/site/markdown/use/variables.md | 37 +++- 10 files changed, 383 insertions(+), 24 deletions(-) create mode 100644 src/site/markdown/use/unenforced.md diff --git a/CI/integration b/CI/integration index 06f7b0154..21ac72516 100644 --- a/CI/integration +++ b/CI/integration @@ -131,6 +131,8 @@ int jFeatureVersion = Runtime.version().major(); String vmopts = "-enableassertions:org.postgresql.pljava... -Xcheck:jni"; +vmopts += " --limit-modules=org.postgresql.pljava.internal"; + if ( 24 <= jFeatureVersion ) { vmopts += " -Djava.security.manager=disallow"; // JEP 486 } else if ( 18 <= jFeatureVersion ) diff --git a/pljava-so/src/main/c/Backend.c b/pljava-so/src/main/c/Backend.c index 2e66d12c7..740e8816f 100644 --- a/pljava-so/src/main/c/Backend.c +++ b/pljava-so/src/main/c/Backend.c @@ -2101,10 +2101,10 @@ void Backend_warnJEP411(bool isCommit) "enforce policy in Java versions up to and including 23, " "and Java 17 and 21 are positioned as long-term support releases. " "Java 24 and later can be used, if wanted, WITH ABSOLUTELY NO " - "EXPECTATIONS OF SECURITY, by adding " + "EXPECTATIONS OF SECURITY POLICY ENFORCEMENT, by adding " "\"-Djava.security.manager=disallow\" in \"pljava.vmoptions\". " "This mode should be considered only if all Java code to be used " - "is considered completely vetted and trusted. " + "is considered well vetted and trusted. " "For details on how PL/Java will adapt, please bookmark " "https://github.com/tada/pljava/wiki/JEP-411") )); diff --git a/src/site/markdown/install/install.md.vm b/src/site/markdown/install/install.md.vm index ca56fa833..a8fa4e509 100644 --- a/src/site/markdown/install/install.md.vm +++ b/src/site/markdown/install/install.md.vm @@ -97,8 +97,8 @@ you will have to become patient, and read the rest of this page. **You will most probably have to set `pljava.libjvm_location`.** See the next section. -**It is useful to consider `pljava.vmoptions`.** See the -[VM options page][vmop]. +**It is useful to consider `pljava.vmoptions`. For Java 18 or later it is +necessary.** See the [VM options page][vmop]. [vmop]: vmoptions.html @@ -133,11 +133,27 @@ things right on the first try, you might set them after, too.) For example: Then set this variable to the full pathname, including the filename and extension. + The version of Java this variable points to will determine whether PL/Java + can operate [with security policy enforcement][policy] or must be used + [with no policy enforcement][unenforced]. + +`pljava.allow_unenforced` +: When using PL/Java with no policy enforcement, this variable must be set + as described on the [PL/Java with no policy enforcement][unenforced] page. + +`pljava.allow_unenforced_udt` +: When using PL/Java with no policy enforcement, if PL/Java + [mapped user-defined types][mappedudt] are to be used, this variable must + be set as described on the + [PL/Java with no policy enforcement][unenforced] page. + `pljava.vmoptions` -: While it should not be necessary to set these before seeing the first signs - of life from PL/Java, there are useful options to consider setting here - before calling the installation complete. Some are described on the - [VM options page][vmop]. +: JVM options can be set here, a number of which are described on the + [VM options page][vmop]. For the most part, they are not essential to + seeing the first signs of life from PL/Java and can be left for tuning + later. However, on Java 18 and later, it is necessary to choose + a `-Djava.security.manager=...` setting before PL/Java will run at all. + Details are on the [VM options page][vmop]. `pljava.module_path` : There is probably no need to set this variable unless installation locations @@ -218,8 +234,10 @@ specify permissions. The exact permissions granted in either case can be customized in [`pljava.policy`][policy]. -__Note: For implications when running on Java 17 or later, -please see [JEP 411][jep411]__. +__Important: The above description applies when PL/Java is run +[with policy enforcement][policy], available on Java 23 and older. +On stock Java 24 and later, PL/Java can only be run with no policy enforcement, +and the implications should be reviewed carefully [here][unenforced].__ PostgreSQL, by default, would grant `USAGE` to `PUBLIC` on the `java` language, but PL/Java takes a more conservative approach on a new installation. @@ -231,10 +249,15 @@ if a site prefers that traditional policy. In a repeat or upgrade installation (the language `java` already exists), no change will be made to the access permissions granted on it. +When running [with no policy enforcement][unenforced], PL/Java allows only +database superusers to create functions even in the `java` language, +disregarding any `USAGE` grants. + $h2 Special topics Be sure to read these additional sections if: +* You intend to use [Java 24 or later][unenforced] * You are installing on [a system using SELinux][selinux] * You are installing on [Mac OS X][osx] * You are installing on [Ubuntu][ubu] and the self-extracting jar won't work @@ -395,5 +418,6 @@ In this case, simply place the files in any location where you can make them readable by the user running `postgres`, and set the `pljava.*` variables accordingly. -[jep411]: https://github.com/tada/pljava/wiki/JEP-411 [policy]: ../use/policy.html +[unenforced]: ../use/unenforced.html +[mappedudt]: ../pljava-api/apidocs/org.postgresql.pljava/org/postgresql/pljava/annotation/MappedUDT.html diff --git a/src/site/markdown/install/locatejvm.md b/src/site/markdown/install/locatejvm.md index 1d6fab37f..24c4b6f80 100644 --- a/src/site/markdown/install/locatejvm.md +++ b/src/site/markdown/install/locatejvm.md @@ -27,6 +27,18 @@ by a process, this works: strace -e open java 2>&1 | grep libjvm ``` +## Version of the Java library selected + +The library pointed to be `pljava.libjvm_location` must be a Java 9 or later +JVM for the PL/Java 1.6 series. The actual version of the library will determine +what Java language features are available for PL/Java functions to use. + +The Java version also influences whether PL/Java can operate +[with security policy enforcement][policy] or +[with no policy enforcement][unenforced]. For stock Java 24 or later, it is only +possible to operate with no enforcement, and the implications detailed for +[PL/Java with no policy enforcement][unenforced] should be carefully reviewed. + ## Using a less-specific path The methods above may find the `libjvm` object on a very specific path @@ -47,3 +59,7 @@ generic one like `jre`, linked to whichever Java version is considered current. Using an alias that is too generic could possibly invite headaches if the default Java version is ever changed to one your PL/Java modules were not written for (or PL/Java itself was not built for). + + +[policy]: ../use/policy.html +[unenforced]: ../use/unenforced.html diff --git a/src/site/markdown/install/vmoptions.md b/src/site/markdown/install/vmoptions.md index 9a32b203b..563744aaf 100644 --- a/src/site/markdown/install/vmoptions.md +++ b/src/site/markdown/install/vmoptions.md @@ -7,6 +7,26 @@ options are likely to be worth setting. If using [the OpenJ9 JVM][hsj9], be sure to look also at the [VM options specific to OpenJ9][vmoptJ9]. +## Selecting operation with or without security policy enforcement + +PL/Java can operate [with security policy enforcement][policy], its former +default and only mode, or [with no policy enforcement][unenforced], the only +mode available on stock Java 24 and later. + +When `pljava.libjvm_location` points to a Java 17 or earlier JVM, there is +no special VM option needed, and PL/Java will operate with policy enforcement +by default. However, when `pljava.libjvm_location` points to a Java 18 or later +JVM, `pljava.vmoptions` must contain either `-Djava.security.manager=allow` or +`-Djava.security.manager=disallow`, to select operation with or without policy +enforcement, respectively. No setting other than `allow` or `disallow` will +work. Only `disallow` is available for stock Java 24 or later. + +Before operating with `disallow`, the implications detailed in +[PL/Java with no policy enforcement][unenforced] should be carefully reviewed. + +[policy]: ../use/policy.html +[unenforced]: ../use/unenforced.html + ## Adding to the set of readable modules By default, a small set of Java modules (including `java.base`, @@ -25,6 +45,12 @@ that full API available to PL/Java code without further thought. The cost, however, may be that PL/Java uses more memory and starts more slowly than if only a few needed modules were named. +For just that reason, there is also a `--limit-modules` option that can be used +to trim the set of readable modules to the minimum genuinely needed. More on the +use of that option [here][limitmods]. + +[limitmods]: ../use/jpms.html#Limiting_the_module_graph + Third-party modular code can be made available by adding the modular jars to `pljava.module_path` (see [configuration variables](../use/variables.html)) and naming those modules in `--add-modules`. PL/Java currently treats all jars diff --git a/src/site/markdown/use/jpms.md b/src/site/markdown/use/jpms.md index dc635d0d0..4a8b891e0 100644 --- a/src/site/markdown/use/jpms.md +++ b/src/site/markdown/use/jpms.md @@ -83,6 +83,36 @@ refer to any of the Java SE API. However, PL/Java instances may use less memory and start up more quickly if an effort is made to add only modules actually needed. +### Limiting the module graph + +Less conveniently perhaps, but advantageously for memory footprint and quick +startup, the [`--limit-modules`][limitmods] option can be used. As of this +writing in early 2025, starting up a simple PL/Java installation on Java 24 +with no `--add-modules` option results in 48 modules resolved, and the 48 +include some unlikely choices for PL/Java purposes, such as `java.desktop`, +`jdk.unsupported.desktop`, `jdk.javadoc`, and others. + +With the option `--limit-modules=org.postgresql.pljava.internal` added, only +nine modules are resolved---the transitive closure of those required by PL/Java +itself---and all of PL/Java's supplied examples successfully run. + +The `--add-modules` option can then be used to make any other actually-needed +modules available again. Those named with `--add-modules` are implicitly added +to those named with `--limit-modules`, so there is no need to change the +`--limit-modules` setting when adding another module. For example, + +``` +--limit-modules=org.postgresql.pljava.internal --add-modules=java.net.http +``` + +will allow use of `java.net.http` in addition to the nine modules resolved for +PL/Java itself. + +Limiting the module graph can be especially advisable when running PL/Java with +no security policy enforcement, as required on stock Java 24 and later. The page +[PL/Java with no policy enforcement][unenforced] should be carefully reviewed +for other implications of running PL/Java that way. + ## Configuring the launch-time module path The configuration variable `pljava.module_path` controls the @@ -115,3 +145,5 @@ character. [jpms]: https://cr.openjdk.java.net/~mr/jigsaw/spec/ [resolution]: https://docs.oracle.com/javase/9/docs/api/java/lang/module/package-summary.html#resolution [addm]: ../install/vmoptions.html#Adding_to_the_set_of_readable_modules +[limitmods]: https://openjdk.org/jeps/261#Limiting-the-observable-modules +[unenforced]: unenforced.html diff --git a/src/site/markdown/use/policy.md b/src/site/markdown/use/policy.md index df423a293..dfdc905b5 100644 --- a/src/site/markdown/use/policy.md +++ b/src/site/markdown/use/policy.md @@ -1,5 +1,16 @@ # Configuring permissions in PL/Java +This page describes how PL/Java operates when enforcing security policy, +available when using Java 23 or earlier. + +When using PL/Java with stock Java 24 or later, please see instead the +[PL/Java without policy enforcement][unenforced] page. + +To operate with policy enforcement as described here, no special configuration +is needed on Java 17 and earlier, while on Java 18 through 23, an entry +`-Djava.security.manager=allow` in [`pljava.vmoptions`][confvar] must be present +for PL/Java to start. + ## `TRUSTED` (and untrusted) procedural languages PostgreSQL allows a procedural language to be installed with or without @@ -8,12 +19,11 @@ can be created in that language by any user (PostgreSQL role) with `USAGE` permission on that language, as configured with the SQL commands `GRANT USAGE ON LANGUAGE ...` and `REVOKE USAGE ON LANGUAGE ...`. For a language that is _not_ designated `TRUSTED`, only a database superuser -may create functions that use it, no matter who has been granted `USAGE` -on it. +may create functions that use it. No `USAGE` permission can be granted on it. In either case, once any function has been created, that function may be executed by any user/role granted `EXECUTE` permission on the function -itself; a language's `USAGE` privilege (plus superuser status, if the language +itself; a language's `USAGE` privilege (or superuser status, if the language is not `TRUSTED`) is only needed to create a function that uses the language. Because PL functions execute in the database server, a general-purpose @@ -121,7 +131,7 @@ installed with PL/Java. The `pljava.policy` file, by default, is used _instead of_ any `.java.policy` file in the OS user's home directory that Java would normally load. There probably is no such file in the `postgres` user's home directory, and if -for any reason there is one, it probably is not tailored to PL/Java. +for any reason there is one, it probably was not put there with PL/Java in mind. The [configuration variable][confvar] `pljava.policy_urls` can be used to name different, or additional, policy files. @@ -371,8 +381,8 @@ That should be regarded as an implementation detail; it may change in a future release, so relying on it is not recommended. The developers of Java have elected to phase out important language features -used by PL/Java to enforce policy. The changes will come in releases after -Java 17. For migration planning, this version of PL/Java can still enable +used by PL/Java to enforce policy. The functionality has been removed in +Java 24. For migration planning, this version of PL/Java can still enable policy enforcement in Java versions up to and including 23, and Java 17 and 21 are positioned as long-term support releases. (There is a likelihood, increasing with later Java versions, even before policy stops being enforceable, @@ -391,4 +401,5 @@ For details on how PL/Java will adapt, please bookmark [sqljajl]: ../pljava/apidocs/org.postgresql.pljava.internal/org/postgresql/pljava/management/Commands.html#alias_java_language [tssec]: https://docs.oracle.com/en/java/javase/14/security/troubleshooting-security.html [trial]: trial.html +[unenforced]: unenforced.html [jep411]: https://github.com/tada/pljava/wiki/JEP-411 diff --git a/src/site/markdown/use/unenforced.md b/src/site/markdown/use/unenforced.md new file mode 100644 index 000000000..15cdaf2df --- /dev/null +++ b/src/site/markdown/use/unenforced.md @@ -0,0 +1,214 @@ +# PL/Java with no policy enforcement + +This page describes how PL/Java operates when it is not enforcing any security +policy, as when running on stock Java 24 or later. + +When the newest Java language features are not needed, it may be preferable to +use a Java 23 or earlier JVM to retain PL/Java's historically fine-grained and +configurable limits on what the Java code can do. For that case, please see +instead the [configuring permissions in PL/Java][policy] page. + +## History: policy enforcement pre-Java 24 + +PL/Java has historically been able to enforce configurable limits on the +behavior of Java code, and to offer more than one "procedural language" with +distinct names, such as `java` and `javau`, for declaring functions with +different limits on what they can do. In PostgreSQL parlance, the language named +without 'u' would be described as 'trusted', meaning any functions created in +that language would run with strict limits. Such functions could be created by +any PostgreSQL user granted `USAGE` permission on that language. The language +named with 'u' would be described as 'untrusted' and impose fewer limits on what +functions can do; accordingly, only PostgreSQL superusers would be allowed to +create functions in such a language. + +PL/Java, going further than many PLs, allowed tailoring of the exact policies +imposed for both `java` and `javau`, and also allowed creation of additional +language aliases beyond those two, with different tailored policies for each. + +Those capabilities remain available when PL/Java is used with Java versions +up through Java 23, and are described more fully in +[configuring permissions in PL/Java][policy]. + +## The present: Java 24 and later, no policy enforcement in PL/Java 1.6 + +The Java language features necessary for policy enforcement in the PL/Java 1.6 +series have been removed from the language as of Java 24. It is possible to +use Java 24 or later with an up-to-date 1.6-series PL/Java, but only by running +with no policy enforcement at all. + +That does not mean only that PL/Java's 'trusted' and 'untrusted' languages are +no longer different: it means that even the 'untrusted' language's more-relaxed +former limits can no longer be enforced. When run with enforcement disabled, +PL/Java is better described as a wholly-'untrusted' PL with nearly no limits on +what the Java code can do. + +The only limits a Java 24 or later runtime can impose on what the Java code can +do are those imposed by the isolation of modules in the +[Java Platform Module System][jpms] and by a small number of VM options, which +will be discussed further below. + +This picture is radically different from the historical one with enforcement. To +run PL/Java in this mode may be a reasonable choice if Java 24 or later language +features are wanted and if all of the Java code to be used is considered well +vetted, thoroughly trusted, and defensively written. + +For news of possible directions for policy enforcement in future PL/Java +versions, please bookmark [this wiki page][jep411]. + +## Opting in to PL/Java with no enforcement + +For PL/Java to run with no policy enforcement (and, therefore, for it to run +at all on Java 24 or later), specific configuration settings must be made to opt +in. + +### In `pljava.vmoptions` + +The string `-Djava.security.manager=disallow` must appear in the setting of +[`pljava.vmoptions`][vmoptions] or PL/Java will be unable to start on Java 24 +or later. + +### in `pljava.allow_unenforced` + +Typically, a PL extension that provides only 'untrusted' execution will define +only a single, untrusted, PL name: `plpython3u` would be an example. + +PL/Java, however: + +* Has historically offered both a `javau` and a trusted `java` PL +* Still can offer both, when run on a Java 23 or older JVM +* May have been installed in a database with functions already created of both + types, and then switched to running on Java 24 and without enforcement +* Can also be switched back to a Java 23 or older JVM and provide enforcement + again + +Therefore, a PL/Java installation still normally provides two (or more) named +PLs, each being declared to PostgreSQL as either 'trusted' or not. + +When running with no enforcement, however: + +* Only PostgreSQL superusers can create functions, even using PL names shown as + 'trusted', and without regard to any grants of `USAGE` on those PLs. + + There may, however, be functions already defined in 'trusted' PLs that were + created by non-superusers with `USAGE` granted, at some earlier time when + PL/Java was running with enforcement. It may be important to audit those + functions' code before allowing them to run. + +* No PL/Java function at all will be allowed to run unless the name of its PL is + included in the `pljava.allow_unenforced` [configuration variable][vbls]. + +* When there are existing PL/Java functions declared in more than one named PL, + they can be audited in separate batches, with the name of each PL added + to the `pljava.allow_unenforced` setting after the functions declared + in that PL have been approved. Or, individual functions, once approved, can + be redeclared with the PL name changed to one already listed in + `pljava.allow_unenforced`. + +* Creation of a new function, even by a superuser, with a PL name not listed in + `pljava.allow_unenforced` will normally raise an error when PL/Java is + running without enforcement. This will not be detected, however, at times + when `check_function_bodies` is `off`, so is better seen as a reminder than + as a form of security. The more-important check is the one made when + the function executes. + +### in `pljava.allow_unenforced_udt` + +Java methods for input and output conversion of PL/Java +[mapped user-defined types][mappedudt], which are executed directly by PL/Java +and have no SQL declarations to carry a PL name, are allowed to execute only if +`pljava.allow_unenforced_udt` is `on`. The table `sqlj.typemap_entry` can be +queried for a list of mapped UDT Java classes to audit before changing this +setting to `on`. + +## Hardening for PL/Java with no policy enforcement + +### External hardening measures + +Developers of the Java language, in their rationale for removing the +Java features needed for policy enforcement, have placed strong emphasis on +available protections at the OS or container level, external to the process +running Java. For the case of PL/Java, that would mean typical hardening +measures such as running PostgreSQL in a container, using [SELinux][selinux], +perhaps in conjunction with [sepgsql][], and so on. + +Those external measures, however, generally confine what the process can do as a +whole. Because PL/Java executes within a PostgreSQL backend process, which must +still be allowed to do everything PostgreSQL itself does, it is difficult for an +external measure to restrict what Java code can do any more narrowly than that. + +### Java hardening measures + +Java features do remain that can be used to put some outer guardrails on what +the Java code can do. They include some specific settings that can be made in +`pljava.vmoptions`, and the module-isolation features of the +[Java Platform Module System][jpms] generally. These should be conscientiously +used: + +#### `--sun-misc-unsafe-memory-access=deny` + +This setting is first available in Java 23. It should be used whenever +available, and especially in Java 24 or later with no policy enforcement. +Without this setting, and in the absence of policy enforcement, any Java code +can access memory in ways that break the Java object model. + +The only reason not to set this option would be when knowingly using a Java +library that requires the access, if there is no update or alternative to using +that library. More modern code would use later APIs for which access can be +selectively granted to specific modules. + +#### `--illegal-native-access=deny` + +This setting is first available in Java 24 and should be used whenever +available. Without this setting, in the absence of policy enforcement, +any Java code can execute native code. There is arguably no good reason to +relax this setting, as options already exist to selectively grant such access +to specific modules that need it, if any. + +#### Module system protections + +Java's module system is one of the most important remaining mechanisms for +limiting what Java code may be able to do. Keeping unneeded modules out of the +module graph, advantageous already for startup speed and memory footprint, +also means whatever those modules do won't be available to Java code. + +The `--limit-modules` VM option can be effectively used to resolve fewer modules +when PL/Java loads. As of this writing, in early 2025, starting PL/Java with no +`--add-modules` or `--limit-modules` options results in 48 modules in the graph, +while a simple `--limit-modules=org.postgresql.pljava.internal` added to +`pljava.vmoptions` reduces the graph to nine modules---all the transitive +requirements of PL/Java itself---and all of PL/Java's supplied examples +successfully run. Any additional modules needed for user code can be added back +with `--add-modules`. More details at [Limiting the module graph][limiting]. + +The `--sun-misc-unsafe-memory-access=deny` option mentioned above denies access +to certain methods of the `sun.misc.Unsafe` class, which is supplied by +the `jdk.unsupported` module. It may be preferable, when there is no other need +for it, to also make sure `jdk.unsupported` is not present in the module graph +at all. + +##### Modularize code needing special access + +It is currently less convenient in PL/Java 1.6 to provide user code in modular +form: the `sqlj.install_jar` and `sqlj.set_classpath` functions manage a class +path, not a module path. Supplying a module requires placing it on the file +system and adding it to `pljava.module_path`. + +The extra inconvenience may be worthwhile in some cases where there is a subset +of code that requires special treatment, such as an exception to the native +access restriction. Placing just that code into a named module on the module +path allows the exception to be made just for that module by name. With the +removal of Java's former fine-grained policy permissions, such module-level +exceptions are the finest-grained controls remaining in stock Java. + +For news of possible directions for policy enforcement in future PL/Java +versions, please bookmark [this wiki page][jep411]. + +[policy]: policy.html +[jpms]: jpms.html +[vmoptions]: ../install/vmoptions.html +[vbls]: variables.html +[jep411]: https://github.com/tada/pljava/wiki/JEP-411 +[selinux]: ../install/selinux.html +[sepgsql]: https://www.postgresql.org/docs/17/sepgsql.html +[limiting]: jpms.html#Limiting_the_module_graph +[mappedudt]: ../pljava-api/apidocs/org.postgresql.pljava/org/postgresql/pljava/annotation/MappedUDT.html diff --git a/src/site/markdown/use/use.md b/src/site/markdown/use/use.md index 973717516..547ac462b 100644 --- a/src/site/markdown/use/use.md +++ b/src/site/markdown/use/use.md @@ -46,9 +46,14 @@ By default, PL/Java code can see a small set of Java modules, including ### Configuring permissions -The permissions in effect for PL/Java functions can be tailored, independently -for functions declared to the `TRUSTED` or untrusted language, as described -[here](policy.html). +When PL/Java is used with Java 23 or earlier, the permissions in effect +for PL/Java functions can be tailored, independently for functions declared to +the `TRUSTED` or untrusted language, as described [here](policy.html). + +When PL/Java is used with stock Java 24 or later, no such tailoring of +permissions is possible, and the +[PL/Java with no policy enforcement](unenforced.html) page should be carefully +reviewed. #### Tailoring permissions for code migrated from PL/Java pre-1.6 diff --git a/src/site/markdown/use/variables.md b/src/site/markdown/use/variables.md index a3cf32841..7f2878ee3 100644 --- a/src/site/markdown/use/variables.md +++ b/src/site/markdown/use/variables.md @@ -30,6 +30,21 @@ These PostgreSQL configuration variables can influence PL/Java's operation: define what any values outside ASCII represent; it is usable, but [subject to limitations][sqlascii]. +`pljava.allow_unenforced` +: Only used when PL/Java is run with no policy enforcement, this setting is + a list of language names (such as `javau` and `java`) in which functions + will be allowed to execute. This setting has an empty default, and should + only be changed after careful review of the + [PL/Java with no policy enforcement][unenforced] page. + +`pljava.allow_unenforced_udt` +: Only used when PL/Java is run with no policy enforcement, this on/off + setting controls whether data conversion functions associated with + PL/Java [mapped user-defined types][mappedudt] + will be allowed to execute. This setting defaults to off, and should + only be changed after careful review of the + [PL/Java with no policy enforcement][unenforced] page. + `pljava.debug` : A boolean variable that, if set `on`, stops the process on first entry to PL/Java before the Java virtual machine is started. The process cannot @@ -92,6 +107,10 @@ These PostgreSQL configuration variables can influence PL/Java's operation: object (filename typically ending with `.so`, `.dll`, or `.dylib`). To determine the proper setting, see [finding the `libjvm` library][fljvm]. + The version of the Java library pointed to by this variable will determine + whether PL/Java can run [with security policy enforcement][policy] or + [with no policy enforcement][unenforced]. + `pljava.module_path` : The module path to be passed to the Java application class loader. The default is computed from the PostgreSQL configuration and is usually correct, unless @@ -109,9 +128,11 @@ These PostgreSQL configuration variables can influence PL/Java's operation: [PL/Java and the Java Platform Module System](jpms.html). `pljava.policy_urls` -: A list of URLs to Java security [policy files](policy.html) determining - the permissions available to PL/Java functions. Each URL should be - enclosed in double quotes; any double quote that is literally part of +: Only used when PL/Java is running [with security policy enforcement][policy]. + When running [with no policy enforcement][unenforced], this variable is + ignored. It is a list of URLs to Java security [policy files][policy] + determining the permissions available to PL/Java functions. Each URL should + be enclosed in double quotes; any double quote that is literally part of the URL may be represented as two double quotes (in SQL style) or as `%22` in the URL convention. Between double-quoted URLs, a comma is the list delimiter. @@ -174,7 +195,12 @@ These PostgreSQL configuration variables can influence PL/Java's operation: may be adjusted in a future PL/Java version. Some important settings can be made here, and are described on the - [VM options page][vmop]. + [VM options page][vmop]. For Java 18 and later, this variable must include + a `-Djava.security.manager=allow` or `-Djava.security.manager=disallow]` + setting, determining whether PL/Java will run + [with security policy enforcement][policy] or + [with no policy enforcement][unenforced], and those pages should be reviewed + for the implications of the choice. [pre92]: ../install/prepg92.html [depdesc]: https://github.com/tada/pljava/wiki/Sql-deployment-descriptor @@ -186,3 +212,6 @@ These PostgreSQL configuration variables can influence PL/Java's operation: [vmop]: ../install/vmoptions.html [sqlascii]: charsets.html#Using_PLJava_with_server_encoding_SQL_ASCII [addm]: ../install/vmoptions.html#Adding_to_the_set_of_readable_modules +[policy]: policy.html +[unenforced]: unenforced.html +[mappedudt]: ../pljava-api/apidocs/org.postgresql.pljava/org/postgresql/pljava/annotation/MappedUDT.html From a780da7012057ba5840ad8afeb82cc1cb7517235 Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Sun, 23 Feb 2025 19:19:05 -0500 Subject: [PATCH 07/12] Defensively copy system properties There are so many things resting on assumptions that various properties aren't just freely modifiable by any code anywhere. Add an unmodifiable FrozenProperties final subclass of Properties, and populate it with a defensive copy just after InstallHelper has set the PL/Java-specific properties. Make this frozen copy available to user code through the Session API. The above is not enough by itself, because PL/Java internally uses some properties in code that executes before that point in PL/Java startup. So those places have to be fixed to do their own defensive caching. ELogFormatter is easy because writing a newline can be done with PrintWriter.println(), and that uses a copy of line.separator already cached early in the JVM startup. The determination of the byte ordering for SQLInputFromChunk and SQLOutputToChunk is more work. Just move all that logic out of InstallHelper and into a new class that just serves as a cache for that, and arrange for the new class to be initialized at the right point in PL/Java startup. --- .../java/org/postgresql/pljava/Session.java | 19 ++- .../org/postgresql/pljava/SessionManager.java | 38 ++++- pljava-so/src/main/c/Backend.c | 4 +- pljava-so/src/main/c/SQLChunkIOOrder.c | 36 ++++ .../postgresql/pljava/elog/ELogFormatter.java | 6 +- .../pljava/internal/InstallHelper.java | 36 +--- .../postgresql/pljava/internal/Session.java | 28 ++- .../pljava/jdbc/SQLChunkIOOrder.java | 114 +++++++++++++ .../pljava/jdbc/SQLInputFromChunk.java | 42 +---- .../pljava/jdbc/SQLOutputToChunk.java | 42 +---- .../pljava/nopolicy/FrozenProperties.java | 159 ++++++++++++++++++ .../pljava/nopolicy/package-info.java | 6 + src/site/markdown/use/unenforced.md | 22 +++ 13 files changed, 433 insertions(+), 119 deletions(-) create mode 100644 pljava-so/src/main/c/SQLChunkIOOrder.c create mode 100644 pljava/src/main/java/org/postgresql/pljava/jdbc/SQLChunkIOOrder.java create mode 100644 pljava/src/main/java/org/postgresql/pljava/nopolicy/FrozenProperties.java create mode 100644 pljava/src/main/java/org/postgresql/pljava/nopolicy/package-info.java diff --git a/pljava-api/src/main/java/org/postgresql/pljava/Session.java b/pljava-api/src/main/java/org/postgresql/pljava/Session.java index 4dbc96947..2169bb644 100644 --- a/pljava-api/src/main/java/org/postgresql/pljava/Session.java +++ b/pljava-api/src/main/java/org/postgresql/pljava/Session.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2022 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -17,6 +17,8 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.Properties; + /** * A Session brings together some useful methods and data for the current * database session. It provides a set of attributes (a @@ -35,6 +37,21 @@ */ public interface Session { + /** + * Returns an unmodifiable defensive copy of the Java + * {@link System#getProperties() system properties} taken early in PL/Java + * startup before user code has an opportunity to write them. + *

+ * When PL/Java is running without security policy enforcement, as on stock + * Java 24 and later, using the frozen properties can simplify defensive + * coding against the possibility of arbitrary property modifications. + * + * @return a {@link Properties} object that departs from the API spec by + * throwing {@link UnsupportedOperationException} from any method if the + * properties would otherwise be modified. + */ + Properties frozenSystemProperties(); + /** * Adds the specified {@code listener} to the list of listeners that will * receive savepoint events. An {@link AccessControlContext} saved by this diff --git a/pljava-api/src/main/java/org/postgresql/pljava/SessionManager.java b/pljava-api/src/main/java/org/postgresql/pljava/SessionManager.java index 5a8c0ed77..3211e4db0 100644 --- a/pljava-api/src/main/java/org/postgresql/pljava/SessionManager.java +++ b/pljava-api/src/main/java/org/postgresql/pljava/SessionManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2019 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -23,21 +23,41 @@ */ public class SessionManager { - private static Session s_session; - /** * Returns the current session. */ public static Session current() throws SQLException { - if(s_session == null) + try + { + return Holder.s_session; + } + catch ( ExceptionInInitializerError e ) { - s_session = load( - Session.class.getModule().getLayer(), Session.class) - .findFirst().orElseThrow(() -> new SQLException( - "could not obtain PL/Java Session object")); + Throwable c = e.getCause(); + if ( c instanceof SQLException ) + throw (SQLException)c; + throw e; + } + } + + private static class Holder + { + private static final Session s_session; + + static { + try + { + s_session = load( + Session.class.getModule().getLayer(), Session.class) + .findFirst().orElseThrow(() -> new SQLException( + "could not obtain PL/Java Session object")); + } + catch ( SQLException e ) + { + throw new ExceptionInInitializerError(e); + } } - return s_session; } } diff --git a/pljava-so/src/main/c/Backend.c b/pljava-so/src/main/c/Backend.c index 740e8816f..8e4cf71d0 100644 --- a/pljava-so/src/main/c/Backend.c +++ b/pljava-so/src/main/c/Backend.c @@ -144,6 +144,7 @@ extern void Session_initialize(void); extern void PgSavepoint_initialize(void); extern void XactListener_initialize(void); extern void SubXactListener_initialize(void); +extern void SQLChunkIOOrder_initialize(void); extern void SQLInputFromChunk_initialize(void); extern void SQLOutputToChunk_initialize(void); extern void SQLOutputToTuple_initialize(void); @@ -798,7 +799,7 @@ static void initsequencer(enum initstage is, bool tolerant) /*FALLTHROUGH*/ case IS_PLJAVA_FOUND: - greeting = InstallHelper_hello(); + greeting = InstallHelper_hello(); /*adjusts, freezes system properties*/ ereport(NULL != pljavaLoadPath ? NOTICE : DEBUG1, ( errmsg("PL/Java loaded"), errdetail("versions:\n%s", greeting))); @@ -1094,6 +1095,7 @@ static void initPLJavaClasses(void) PgSavepoint_initialize(); XactListener_initialize(); SubXactListener_initialize(); + SQLChunkIOOrder_initialize(); /* safely caches relevant system properties */ SQLInputFromChunk_initialize(); SQLOutputToChunk_initialize(); SQLOutputToTuple_initialize(); diff --git a/pljava-so/src/main/c/SQLChunkIOOrder.c b/pljava-so/src/main/c/SQLChunkIOOrder.c new file mode 100644 index 000000000..8663eeaff --- /dev/null +++ b/pljava-so/src/main/c/SQLChunkIOOrder.c @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025 TADA AB and other contributors, as listed below. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the The BSD 3-Clause License + * which accompanies this distribution, and is available at + * http://opensource.org/licenses/BSD-3-Clause + * + * Contributors: + * Chapman Flack + */ +#include + +#include "pljava/PgObject.h" + +extern void SQLChunkIOOrder_initialize(void); +void SQLChunkIOOrder_initialize(void) +{ + /* + * Nothing more is needed here than to cause the class's static initializer + * to run (at the chosen time, from native code, before user Java code could + * have altered the needed system properties). + * + * The JNI_FindClass mentions that it initializes the named class, but only + * says so in one place, does not clearly say it returns an initialized + * class, and does not mention ExceptionInInitializerError as a possible + * exception. + * + * GetStaticFieldID clearly says it causes an uninitialized class to be + * initialized, and lists ExceptionInInitializerError as a possible + * exception. So, just to be sure, a field ID is fetched here. + */ + jclass cls = PgObject_getJavaClass( + "org/postgresql/pljava/jdbc/SQLChunkIOOrder"); + PgObject_getStaticJavaField(cls, "MIRROR_J2P", "Ljava/nio/ByteOrder;"); +} diff --git a/pljava/src/main/java/org/postgresql/pljava/elog/ELogFormatter.java b/pljava/src/main/java/org/postgresql/pljava/elog/ELogFormatter.java index 8d60b04e5..ec7ca311e 100644 --- a/pljava/src/main/java/org/postgresql/pljava/elog/ELogFormatter.java +++ b/pljava/src/main/java/org/postgresql/pljava/elog/ELogFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2020 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -29,8 +29,6 @@ public class ELogFormatter extends Formatter private final static MessageFormat s_tsFormatter = new MessageFormat( "{0,date,dd MMM yy} {0,time,HH:mm:ss} {1} {2}"); - private final static String s_lineSeparator = System.getProperty("line.separator"); - private final Date m_timestamp = new Date(); private final Object m_args[] = new Object[] { m_timestamp, null, null }; private final StringBuffer m_buffer = new StringBuffer(); @@ -54,9 +52,9 @@ public synchronized String format(LogRecord record) Throwable thrown = record.getThrown(); if(thrown != null) { - sb.append(s_lineSeparator); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); + pw.println(); /* line.separator safely cached in JVM initPhase1 */ record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); diff --git a/pljava/src/main/java/org/postgresql/pljava/internal/InstallHelper.java b/pljava/src/main/java/org/postgresql/pljava/internal/InstallHelper.java index 0e3f7f585..a2e05c986 100644 --- a/pljava/src/main/java/org/postgresql/pljava/internal/InstallHelper.java +++ b/pljava/src/main/java/org/postgresql/pljava/internal/InstallHelper.java @@ -40,6 +40,7 @@ import org.postgresql.pljava.jdbc.SQLUtils; import org.postgresql.pljava.management.SQLDeploymentDescriptor; +import org.postgresql.pljava.nopolicy.FrozenProperties; import org.postgresql.pljava.policy.TrialPolicy; import static org.postgresql.pljava.annotation.processing.DDRWriter.eQuote; import static org.postgresql.pljava.elog.ELogHandler.LOG_WARNING; @@ -128,33 +129,6 @@ public static String hello( */ setPropertyIfNull( "sqlj.defaultconnection", "jdbc:default:connection"); - /* - * Set the org.postgresql.pljava.udt.byteorder.{scalar,mirror}.{p2j,j2p} - * properties. For shorthand, defaults can be given in shorter property - * keys org.postgresql.pljava.udt.byteorder.{scalar,mirror} or even just - * org.postgresql.pljava.udt.byteorder for an overall default. These - * shorter keys are then removed from the system properties. - */ - String orderKey = "org.postgresql.pljava.udt.byteorder"; - String orderAll = System.getProperty(orderKey); - String orderScalar = System.getProperty(orderKey + ".scalar"); - String orderMirror = System.getProperty(orderKey + ".mirror"); - - if ( null == orderScalar ) - orderScalar = null != orderAll ? orderAll : "big_endian"; - if ( null == orderMirror ) - orderMirror = null != orderAll ? orderAll : "native"; - - setPropertyIfNull(orderKey + ".scalar.p2j", orderScalar); - setPropertyIfNull(orderKey + ".scalar.j2p", orderScalar); - - setPropertyIfNull(orderKey + ".mirror.p2j", orderMirror); - setPropertyIfNull(orderKey + ".mirror.j2p", orderMirror); - - System.clearProperty(orderKey); - System.clearProperty(orderKey + ".scalar"); - System.clearProperty(orderKey + ".mirror"); - String encodingKey = "org.postgresql.server.encoding"; String encName = System.getProperty(encodingKey); if ( null == encName ) @@ -180,6 +154,14 @@ public static String hello( setPolicyURLs(); } + /* + * PL/Java modifies no more system properties beyond this point. + * Take a defensive copy here that can be exposed through the Session + * API. + */ + org.postgresql.pljava.internal.Session.s_properties = + new FrozenProperties(System.getProperties()); + /* * Construct the strings announcing the versions in use. */ diff --git a/pljava/src/main/java/org/postgresql/pljava/internal/Session.java b/pljava/src/main/java/org/postgresql/pljava/internal/Session.java index cb44c12b7..ca55332e3 100644 --- a/pljava/src/main/java/org/postgresql/pljava/internal/Session.java +++ b/pljava/src/main/java/org/postgresql/pljava/internal/Session.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2022 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -19,6 +19,8 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; +import static java.util.Objects.requireNonNull; +import java.util.Properties; import org.postgresql.pljava.ObjectPool; import org.postgresql.pljava.PooledObject; @@ -49,8 +51,16 @@ public static Session provider() return Holder.INSTANCE; } + private final Properties m_properties; + private Session() { + /* + * This strategy assumes that no user code will request a Session + * instance until after InstallHelper has poked the frozen properties + * into s_properties. + */ + m_properties = requireNonNull(s_properties); } private static class Holder @@ -58,8 +68,11 @@ private static class Holder static final Session INSTANCE = new Session(); } - @SuppressWarnings("removal") - private final TransactionalMap m_attributes = new TransactionalMap(new HashMap()); + /** + * An unmodifiable defensive copy of the Java system properties that will be + * put here by InstallHelper via package access at startup. + */ + static Properties s_properties; /** * The Java charset corresponding to the server encoding, or null if none @@ -67,6 +80,12 @@ private static class Holder */ static Charset s_serverCharset; + @Override + public Properties frozenSystemProperties() + { + return m_properties; + } + /** * A static method (not part of the API-exposed Session interface) by which * pljava implementation classes can get hold of the server charset without @@ -86,6 +105,9 @@ public static Charset implServerCharset() return s_serverCharset; } + @SuppressWarnings("removal") + private final TransactionalMap m_attributes = new TransactionalMap(new HashMap()); + /** * Adds the specified listener to the list of listeners that will * receive transactional events. diff --git a/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLChunkIOOrder.java b/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLChunkIOOrder.java new file mode 100644 index 000000000..b7038c950 --- /dev/null +++ b/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLChunkIOOrder.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2016-2025 TADA AB and other contributors, as listed below. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the The BSD 3-Clause License + * which accompanies this distribution, and is available at + * http://opensource.org/licenses/BSD-3-Clause + * + * Contributors: + * Chapman Flack + */ +package org.postgresql.pljava.jdbc; + +import java.nio.ByteOrder; + +import java.sql.SQLNonTransientException; + +import java.util.Properties; + +/** + * Caches the scalar and mirror {@code MappedUDT} byte orders as determined by + * system properties during PL/Java startup. + *

+ * This class is initialized from native code ahead of the + * {@link SQLInputFromChunk} and {@link SQLOutputToChunk} classes that depend + * on it. This happens before {@code InstallHelper} has taken and frozen its + * defensive copy of the Java system properties, and also before PL/Java user + * code has potentially run and changed them. + *

+ * This defensive implementation is needed only for the "PL/Java with no + * security policy enforcement" case, as PL/Java's supplied policy file protects + * these properties from modification when policy is being enforced. + */ +class SQLChunkIOOrder +{ + private SQLChunkIOOrder() { } // do not instantiate + + /** + * Byte order for conversion of "mirror" UDT values in the + * Java-to-PostgreSQL direction. + */ + static final ByteOrder MIRROR_J2P; + + /** + * Byte order for conversion of "mirror" UDT values in the + * PostgreSQL-to-Java direction. + */ + static final ByteOrder MIRROR_P2J; + + /** + * Byte order for conversion of "scalar" UDT values in the + * Java-to-PostgreSQL direction. + */ + static final ByteOrder SCALAR_J2P; + + /** + * Byte order for conversion of "scalar" UDT values in the + * PostgreSQL-to-Java direction. + */ + static final ByteOrder SCALAR_P2J; + + static + { + /* + * Set the org.postgresql.pljava.udt.byteorder.{scalar,mirror}.{p2j,j2p} + * properties. For shorthand, defaults can be given in shorter property + * keys org.postgresql.pljava.udt.byteorder.{scalar,mirror} or even just + * org.postgresql.pljava.udt.byteorder for an overall default. These + * shorter keys are then removed from the system properties. + */ + Properties ps = System.getProperties(); + + String orderKey = "org.postgresql.pljava.udt.byteorder"; + String orderAll = ps.getProperty(orderKey); + String orderMirror = ps.getProperty(orderKey + ".mirror"); + String orderScalar = ps.getProperty(orderKey + ".scalar"); + + if ( null == orderMirror ) + orderMirror = null != orderAll ? orderAll : "native"; + if ( null == orderScalar ) + orderScalar = null != orderAll ? orderAll : "big_endian"; + + System.clearProperty(orderKey); + System.clearProperty(orderKey + ".mirror"); + System.clearProperty(orderKey + ".scalar"); + + try + { + MIRROR_J2P = toByteOrder(ps, orderKey + ".mirror.j2p", orderMirror); + MIRROR_P2J = toByteOrder(ps, orderKey + ".mirror.p2j", orderMirror); + SCALAR_J2P = toByteOrder(ps, orderKey + ".scalar.j2p", orderScalar); + SCALAR_P2J = toByteOrder(ps, orderKey + ".scalar.p2j", orderScalar); + } + catch ( SQLNonTransientException e ) + { + throw new ExceptionInInitializerError(e); + } + } + + private static ByteOrder toByteOrder(Properties ps, String k, String dfl) + throws SQLNonTransientException + { + switch ( (String)ps.computeIfAbsent(k, p -> dfl) ) + { + case "big_endian": return ByteOrder.BIG_ENDIAN; + case "little_endian": return ByteOrder.LITTLE_ENDIAN; + case "native": return ByteOrder.nativeOrder(); + default: + throw new SQLNonTransientException( + "System property " + k + + " must be big_endian, little_endian, or native", "F0000"); + } + } +} diff --git a/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLInputFromChunk.java b/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLInputFromChunk.java index 8510e9c10..1522203f2 100644 --- a/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLInputFromChunk.java +++ b/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLInputFromChunk.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2019 TADA AB and other contributors, as listed below. + * Copyright (c) 2004-2025 TADA AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -20,7 +20,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import static java.nio.charset.StandardCharsets.UTF_8; import java.nio.charset.CharsetDecoder; import java.sql.Array; @@ -41,6 +40,9 @@ import org.postgresql.pljava.internal.Backend; +import static org.postgresql.pljava.jdbc.SQLChunkIOOrder.MIRROR_P2J; +import static org.postgresql.pljava.jdbc.SQLChunkIOOrder.SCALAR_P2J; + /** * The SQLInputToChunk uses JNI to read from memory that has been allocated by * the PostgreSQL backend. A user should never make an attempt to create an @@ -56,44 +58,10 @@ public class SQLInputFromChunk implements SQLInput { private ByteBuffer m_bb; - private static ByteOrder scalarOrder; - private static ByteOrder mirrorOrder; - public SQLInputFromChunk(ByteBuffer bb, boolean isJavaBasedScalar) throws SQLException { - m_bb = bb; - if ( isJavaBasedScalar ) - { - if ( null == scalarOrder ) - scalarOrder = getOrder(true); - m_bb.order(scalarOrder); - } - else - { - if ( null == mirrorOrder ) - mirrorOrder = getOrder(false); - m_bb.order(mirrorOrder); - } - } - - private ByteOrder getOrder(boolean isJavaBasedScalar) throws SQLException - { - ByteOrder result; - String key = "org.postgresql.pljava.udt.byteorder." - + ( isJavaBasedScalar ? "scalar" : "mirror" ) + ".p2j"; - String val = System.getProperty(key); - if ( "big_endian".equals(val) ) - result = ByteOrder.BIG_ENDIAN; - else if ( "little_endian".equals(val) ) - result = ByteOrder.LITTLE_ENDIAN; - else if ( "native".equals(val) ) - result = ByteOrder.nativeOrder(); - else - throw new SQLNonTransientException( - "System property " + key + - " must be big_endian, little_endian, or native", "F0000"); - return result; + m_bb = bb.order(isJavaBasedScalar ? SCALAR_P2J : MIRROR_P2J); } @Override diff --git a/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLOutputToChunk.java b/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLOutputToChunk.java index 2cf1c66a0..5524b70c2 100644 --- a/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLOutputToChunk.java +++ b/pljava/src/main/java/org/postgresql/pljava/jdbc/SQLOutputToChunk.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2019 TADA AB and other contributors, as listed below. + * Copyright (c) 2004-2025 TADA AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -20,7 +20,6 @@ import java.math.BigDecimal; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.CharBuffer; import static java.nio.charset.StandardCharsets.UTF_8; import java.nio.charset.CharsetEncoder; @@ -48,6 +47,9 @@ import static org.postgresql.pljava.internal.Backend.doInPG; +import static org.postgresql.pljava.jdbc.SQLChunkIOOrder.MIRROR_J2P; +import static org.postgresql.pljava.jdbc.SQLChunkIOOrder.SCALAR_J2P; + /** * The SQLOutputToChunk uses JNI to build a PostgreSQL StringInfo buffer in * memory. A user should never make an attempt to create an instance of this @@ -66,46 +68,12 @@ public class SQLOutputToChunk implements SQLOutput private long m_handle; private ByteBuffer m_bb; - private static ByteOrder scalarOrder; - private static ByteOrder mirrorOrder; - public SQLOutputToChunk(long handle, ByteBuffer bb, boolean isJavaBasedScalar) throws SQLException { m_handle = handle; - m_bb = bb; - if ( isJavaBasedScalar ) - { - if ( null == scalarOrder ) - scalarOrder = getOrder(true); - m_bb.order(scalarOrder); - } - else - { - if ( null == mirrorOrder ) - mirrorOrder = getOrder(false); - m_bb.order(mirrorOrder); - } - } - - private ByteOrder getOrder(boolean isJavaBasedScalar) throws SQLException - { - ByteOrder result; - String key = "org.postgresql.pljava.udt.byteorder." - + ( isJavaBasedScalar ? "scalar" : "mirror" ) + ".j2p"; - String val = System.getProperty(key); - if ( "big_endian".equals(val) ) - result = ByteOrder.BIG_ENDIAN; - else if ( "little_endian".equals(val) ) - result = ByteOrder.LITTLE_ENDIAN; - else if ( "native".equals(val) ) - result = ByteOrder.nativeOrder(); - else - throw new SQLNonTransientException( - "System property " + key + - " must be big_endian, little_endian, or native", "F0000"); - return result; + m_bb = bb.order(isJavaBasedScalar ? SCALAR_J2P : MIRROR_J2P); } @Override diff --git a/pljava/src/main/java/org/postgresql/pljava/nopolicy/FrozenProperties.java b/pljava/src/main/java/org/postgresql/pljava/nopolicy/FrozenProperties.java new file mode 100644 index 000000000..ac8c949f7 --- /dev/null +++ b/pljava/src/main/java/org/postgresql/pljava/nopolicy/FrozenProperties.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2025 Tada AB and other contributors, as listed below. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the The BSD 3-Clause License + * which accompanies this distribution, and is available at + * http://opensource.org/licenses/BSD-3-Clause + * + * Contributors: + * Chapman Flack + */ +package org.postgresql.pljava.nopolicy; + +import java.io.InputStream; +import java.io.Reader; + +import static java.util.Arrays.copyOfRange; +import java.util.Collection; +import static java.util.Collections.unmodifiableCollection; +import static java.util.Collections.unmodifiableSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * An unmodifiable subclass of {@link Properties}. + *

+ * The overidden methods violate the superclass API specs to the extent that the + * specs allow modification, or the returning of modifiable sets or collections. + *

+ * When any overridden method would, per the spec, modify the map, the method + * will throw {@link UnsupportedOperationException} instead. + */ +public final class FrozenProperties extends Properties +{ + /** + * Constructs a {@code FrozenProperties} instance from an existing + * {@link Properties} instance. + * @param p the instance whose entries are to be copied + */ + public FrozenProperties(Properties p) + { + // super(p.size()); // has no @Since but first appears in Java 10 + super.putAll(p); + } + + @Override + public Object setProperty(String key, String value) + { + throw readonly(); + } + + @Override + public void load(Reader reader) + { + throw readonly(); + } + + @Override + public void load(InputStream inStream) + { + throw readonly(); + } + + @Override + public void loadFromXML(InputStream in) + { + throw readonly(); + } + + @Override + public void clear() + { + throw readonly(); + } + + @Override + public Object computeIfAbsent( + Object key, Function mappingFunction) + { + Object v = get(key); + if ( null != v ) + return v; + v = mappingFunction.apply(key); + if ( null != v ) + throw readonly(); + return null; + } + + @Override + public Object computeIfPresent( + Object key, BiFunction remappingFunction) + { + Object v = get(key); + if ( null == v ) + return null; + v = remappingFunction.apply(key, v); // if it throws, let it. Else: + throw readonly(); + } + + @Override + public Set> entrySet() + { + return unmodifiableSet(super.entrySet()); + } + + @Override + public Set keySet() + { + return unmodifiableSet(super.keySet()); + } + + @Override + public Object merge(Object key, Object value, + BiFunction remappingFunction) + { + throw readonly(); + } + + @Override + public Object put(Object key, Object value) + { + throw readonly(); + } + + @Override + public void putAll(Map t) + { + if ( 0 < t.size() ) + throw readonly(); + } + + @Override + public Object remove(Object key) + { + Object v = get(key); + if ( null != v ) + throw readonly(); + return null; + } + + @Override + public Collection values() + { + return unmodifiableCollection(super.values()); + } + + private static UnsupportedOperationException readonly() + { + UnsupportedOperationException e = + new UnsupportedOperationException("FrozenProperties modification"); + StackTraceElement[] t = e.getStackTrace(); + e.setStackTrace(copyOfRange(t, 1, t.length)); + return e; + } +} diff --git a/pljava/src/main/java/org/postgresql/pljava/nopolicy/package-info.java b/pljava/src/main/java/org/postgresql/pljava/nopolicy/package-info.java new file mode 100644 index 000000000..4b122cae1 --- /dev/null +++ b/pljava/src/main/java/org/postgresql/pljava/nopolicy/package-info.java @@ -0,0 +1,6 @@ +/** + * Java classes needed to preserve any semblance of a reliable environment + * in Java 24 and later with no security policy enforcement. + * @author Chapman Flack + */ +package org.postgresql.pljava.nopolicy; diff --git a/src/site/markdown/use/unenforced.md b/src/site/markdown/use/unenforced.md index 15cdaf2df..dc8625bc2 100644 --- a/src/site/markdown/use/unenforced.md +++ b/src/site/markdown/use/unenforced.md @@ -203,6 +203,28 @@ exceptions are the finest-grained controls remaining in stock Java. For news of possible directions for policy enforcement in future PL/Java versions, please bookmark [this wiki page][jep411]. +### Defensive coding + +#### Java system properties + +It can be laborious to audit a code base for assumptions that a given Java +system property has a value that is reliable. In the case of no policy +enforcement, when any system property can be changed by any code at any time, +best practice is to rely on defensive copies taken early, before arbitrary +user code can have run. + +For example, `PrintWriter.println` uses a copy of the `line.separator` property +taken early in the JVM's own initialization, so code that relies on `println` to +write a newline will be more dependable than code using `line.separator` +directly. + +PL/Java itself takes a defensive copy of all system properties early in its own +startup, immediately after adding the properties that PL/Java sets. The +`frozenSystemProperties` method of the `org.postgresql.pljava.Session` object +returns this defensive copy, as a subclass of `java.util.Properties` that is +unmodifiable (throwing `UnsupportedOperationException` from methods where a +modification would otherwise result). + [policy]: policy.html [jpms]: jpms.html [vmoptions]: ../install/vmoptions.html From 192e910185bc384b7d76a476f23bf23ae77b3d5c Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Sun, 23 Feb 2025 19:19:11 -0500 Subject: [PATCH 08/12] Use defensive system-property copies in examples --- .../pljava/example/annotation/JDBC42_21.java | 11 ++++++++--- .../pljava/example/annotation/PassXML.java | 9 ++++++--- .../pljava/example/annotation/SPIActions.java | 16 +++++++++++----- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/JDBC42_21.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/JDBC42_21.java index ff2c23116..dde2eaabf 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/JDBC42_21.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/JDBC42_21.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2023 Tada AB and other contributors, as listed below. + * Copyright (c) 2018-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -11,6 +11,10 @@ */ package org.postgresql.pljava.example.annotation; +import java.sql.SQLException; + +import org.postgresql.pljava.SessionManager; + import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.SQLAction; @@ -133,9 +137,10 @@ public class JDBC42_21 * recent as the argument ('1.6', '1.7', '1.8', '9', '10', '11', ...). */ @Function(schema="javatest", provides="javaSpecificationGE") - public static boolean javaSpecificationGE(String want) + public static boolean javaSpecificationGE(String want) throws SQLException { - String got = System.getProperty("java.specification.version"); + String got = SessionManager.current().frozenSystemProperties() + .getProperty("java.specification.version"); if ( want.startsWith("1.") ) want = want.substring(2); if ( got.startsWith("1.") ) diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PassXML.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PassXML.java index 5f3886886..d6dd14bfc 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PassXML.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/PassXML.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2024 Tada AB and other contributors, as listed below. + * Copyright (c) 2018-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -67,6 +67,7 @@ import org.postgresql.pljava.Adjusting; import static org.postgresql.pljava.Adjusting.XML.setFirstSupported; +import org.postgresql.pljava.SessionManager; import org.postgresql.pljava.annotation.Function; import org.postgresql.pljava.annotation.MappedUDT; import org.postgresql.pljava.annotation.SQLAction; @@ -643,7 +644,8 @@ public static SQLXML transformXML( */ if ( rlt instanceof StreamResult ) t.setOutputProperty(ENCODING, - System.getProperty("org.postgresql.server.encoding")); + SessionManager.current().frozenSystemProperties() + .getProperty("org.postgresql.server.encoding")); else if ( Boolean.TRUE.equals(indent) ) logMessage("WARNING", "indent requested, but howout specifies a non-stream " + @@ -712,7 +714,8 @@ private static SQLXML echoSQLXML(SQLXML sx, int howin, int howout) */ if ( howout < 5 ) t.setOutputProperty(ENCODING, - System.getProperty("org.postgresql.server.encoding")); + SessionManager.current().frozenSystemProperties() + .getProperty("org.postgresql.server.encoding")); t.transform(src, rlt); } catch ( TransformerException te ) diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/SPIActions.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/SPIActions.java index dca34e5c7..baf3861e6 100644 --- a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/SPIActions.java +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/SPIActions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004-2020 Tada AB and other contributors, as listed below. + * Copyright (c) 2004-2025 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License @@ -127,20 +127,26 @@ public static String getTimeAsString() throws SQLException { } } - static void log(String msg) { + static void log(String msg) throws SQLException { // GCJ has a somewhat serious bug (reported) // - if ("GNU libgcj".equals(System.getProperty("java.vm.name"))) { + if ("GNU libgcj" + .equals( + SessionManager.current().frozenSystemProperties() + .getProperty("java.vm.name"))) { System.out.print("INFO: "); System.out.println(msg); } else Logger.getAnonymousLogger().info(msg); } - static void warn(String msg) { + static void warn(String msg) throws SQLException { // GCJ has a somewhat serious bug (reported) // - if ("GNU libgcj".equals(System.getProperty("java.vm.name"))) { + if ("GNU libgcj" + .equals( + SessionManager.current().frozenSystemProperties() + .getProperty("java.vm.name"))) { System.out.print("WARNING: "); System.out.println(msg); } else From c4e55d51379fc20b8276dc5ad28491a374b5b8a4 Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Mon, 24 Feb 2025 17:01:22 -0500 Subject: [PATCH 09/12] Handle defaulted properties in FrozenProperties It is distinguishable whether a property was defaulted rather than explicitly set (getProperty(n) finds it but containsKey(n) does not), so the same should be true of the FrozenProperties. --- .../pljava/nopolicy/FrozenProperties.java | 37 ++++++++++++++++++- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/pljava/src/main/java/org/postgresql/pljava/nopolicy/FrozenProperties.java b/pljava/src/main/java/org/postgresql/pljava/nopolicy/FrozenProperties.java index ac8c949f7..f4b00e687 100644 --- a/pljava/src/main/java/org/postgresql/pljava/nopolicy/FrozenProperties.java +++ b/pljava/src/main/java/org/postgresql/pljava/nopolicy/FrozenProperties.java @@ -25,6 +25,8 @@ import java.util.function.BiFunction; import java.util.function.Function; +import static java.util.stream.Collectors.toSet; + /** * An unmodifiable subclass of {@link Properties}. *

@@ -39,12 +41,43 @@ public final class FrozenProperties extends Properties /** * Constructs a {@code FrozenProperties} instance from an existing * {@link Properties} instance. + *

+ * The instance will have a defaults list (also frozen) if p has + * defaults that have not been superseded by later settings. Defaults are + * flattened into a single default properties instance, even if p + * had a defaults instance chaining to another or a chain of others. * @param p the instance whose entries are to be copied */ public FrozenProperties(Properties p) { - // super(p.size()); // has no @Since but first appears in Java 10 - super.putAll(p); + super(defaults(p)); + super.putAll(p); // putAll copies only non-default entries + } + + /** + * Constructor used internally to return a frozen instance with only + * p's defaults (entries with keys in subset). + */ + private FrozenProperties(Properties p, Set subset) + { + // super(subset.size()); // has no @Since but first appears in Java 10 + for ( String s : subset ) + super.put(s, p.get(s)); + } + + /** + * Returns a {@code FrozenProperties} instance representing defaults of + * p not superseded by later settings. + * @return FrozenProperties with the defaults, or null if none + */ + private static FrozenProperties defaults(Properties p) + { + Set defaultedNames = + p.stringPropertyNames().stream().filter(n -> ! p.containsKey(n)) + .collect(toSet()); + if ( defaultedNames.isEmpty() ) + return null; + return new FrozenProperties(p, defaultedNames); } @Override From 43ab7c35b98e83a58d8ba06654eed6d941dce289 Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Thu, 27 Feb 2025 15:04:49 -0500 Subject: [PATCH 10/12] Add example code to see Java's boot module layer The hardening advice in unenforced.md refers to the number of modules resolved into the boot layer, but didn't offer an easy way to check. --- .../pljava/example/annotation/Modules.java | 85 +++++++++++++++++++ src/site/markdown/use/jpms.md | 6 ++ src/site/markdown/use/unenforced.md | 6 ++ 3 files changed, 97 insertions(+) create mode 100644 pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Modules.java diff --git a/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Modules.java b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Modules.java new file mode 100644 index 000000000..b2e9826a1 --- /dev/null +++ b/pljava-examples/src/main/java/org/postgresql/pljava/example/annotation/Modules.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2025 Tada AB and other contributors, as listed below. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the The BSD 3-Clause License + * which accompanies this distribution, and is available at + * http://opensource.org/licenses/BSD-3-Clause + * + * Contributors: + * Chapman Flack + */ +package org.postgresql.pljava.example.annotation; + +import java.lang.module.ModuleDescriptor; + +import java.sql.ResultSet; +import java.sql.SQLException; + +import java.util.Iterator; +import java.util.Objects; + +import java.util.stream.Stream; + +import org.postgresql.pljava.ResultSetProvider; +import org.postgresql.pljava.annotation.Function; +import static org.postgresql.pljava.annotation.Function.Effects.STABLE; + +/** + * Example code to support querying for the modules in Java's boot layer. + */ +public class Modules implements ResultSetProvider.Large { + /** + * Returns information on the named modules in Java's boot module layer. + */ + @Function( + effects = STABLE, + out = { + "name pg_catalog.text", + "any_unqualified_exports boolean", + "any_unqualified_opens boolean" + } + ) + public static ResultSetProvider java_modules() + { + return new Modules( + ModuleLayer.boot().modules().stream().map(Module::getDescriptor) + .filter(Objects::nonNull)); + } + + private final Iterator iterator; + private final Runnable closer; + + private Modules(Stream s) + { + iterator = s.iterator(); + closer = s::close; + } + + @Override + public boolean assignRowValues(ResultSet receiver, long currentRow) + throws SQLException + { + if ( ! iterator.hasNext() ) + return false; + + ModuleDescriptor md = iterator.next(); + + receiver.updateString(1, md.name()); + + receiver.updateBoolean(2, + md.exports().stream().anyMatch(e -> ! e.isQualified())); + + receiver.updateBoolean(3, + md.isOpen() || + md.opens().stream().anyMatch(o -> ! o.isQualified())); + + return true; + } + + @Override + public void close() + { + closer.run(); + } +} diff --git a/src/site/markdown/use/jpms.md b/src/site/markdown/use/jpms.md index 4a8b891e0..848e8c809 100644 --- a/src/site/markdown/use/jpms.md +++ b/src/site/markdown/use/jpms.md @@ -113,6 +113,10 @@ no security policy enforcement, as required on stock Java 24 and later. The page [PL/Java with no policy enforcement][unenforced] should be carefully reviewed for other implications of running PL/Java that way. +The supplied [examples jar][examples] provides a function, [java_modules][], +that can be used to see what modules have been resolved into Java's boot module +layer. + ## Configuring the launch-time module path The configuration variable `pljava.module_path` controls the @@ -147,3 +151,5 @@ character. [addm]: ../install/vmoptions.html#Adding_to_the_set_of_readable_modules [limitmods]: https://openjdk.org/jeps/261#Limiting-the-observable-modules [unenforced]: unenforced.html +[examples]: ../examples/examples.html +[java_modules]: ../pljava-examples/apidocs/index.html?org/postgresql/pljava/example/annotation/Modules.html diff --git a/src/site/markdown/use/unenforced.md b/src/site/markdown/use/unenforced.md index dc8625bc2..5a36bc8f5 100644 --- a/src/site/markdown/use/unenforced.md +++ b/src/site/markdown/use/unenforced.md @@ -171,6 +171,10 @@ limiting what Java code may be able to do. Keeping unneeded modules out of the module graph, advantageous already for startup speed and memory footprint, also means whatever those modules do won't be available to Java code. +The supplied [examples jar][examples] provides a function, [java_modules][], +that can be used to see what modules have been resolved into Java's boot module +layer. + The `--limit-modules` VM option can be effectively used to resolve fewer modules when PL/Java loads. As of this writing, in early 2025, starting PL/Java with no `--add-modules` or `--limit-modules` options results in 48 modules in the graph, @@ -234,3 +238,5 @@ modification would otherwise result). [sepgsql]: https://www.postgresql.org/docs/17/sepgsql.html [limiting]: jpms.html#Limiting_the_module_graph [mappedudt]: ../pljava-api/apidocs/org.postgresql.pljava/org/postgresql/pljava/annotation/MappedUDT.html +[examples]: ../examples/examples.html +[java_modules]: ../pljava-examples/apidocs/index.html?org/postgresql/pljava/example/annotation/Modules.html From 621505f8f13afade82fe25ea775654fb3bcd0c5b Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Thu, 27 Feb 2025 16:24:44 -0500 Subject: [PATCH 11/12] Java version vs. java.security.manager settings The table from the pull request #512 comment would be useful to have in the actual docs. --- src/site/markdown/install/smproperty.md | 49 +++++++++++++++++++++++++ src/site/markdown/install/vmoptions.md | 4 ++ src/site/markdown/use/policy.md | 4 +- src/site/markdown/use/unenforced.md | 4 ++ src/site/markdown/use/variables.md | 4 +- 5 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/site/markdown/install/smproperty.md diff --git a/src/site/markdown/install/smproperty.md b/src/site/markdown/install/smproperty.md new file mode 100644 index 000000000..e58d2dd52 --- /dev/null +++ b/src/site/markdown/install/smproperty.md @@ -0,0 +1,49 @@ +# Available policy-enforcement settings by Java version + +In the PostgreSQL [configuration variable][variables] `pljava.vmoptions`, +whether and how to set the `java.security.manager` property depends on +the Java version in use (that is, on the version of the Java library that +the `pljava.libjvm_location` configuration variable points to). + +There are two ways of setting the `java.security.manager` property that may be +allowed or required depending on the Java version in use. + +`-Djava.security.manager=allow` +: PL/Java's familiar operating mode in which + security policy is enforced. More on that mode can be found in + [Configuring permissions in PL/Java][policy]. + +`-Djava.security.manager=disallow` +: A mode required on Java 24 and later, in which there is no enforcement of + policy. Before setting up PL/Java in this mode, the implications in + [PL/Java with no policy enforcement][unenforced] should be carefully + reviewed. + +This table lays out the requirements by specific version of Java. + +|Java version|Available settings| +|---------|:---| +|9–11|There must be no appearance of `-Djava.security.manager` in `pljava.vmoptions`. Mode will be policy-enforcing.| +|12–17|Either `-Djava.security.manager=allow` or `-Djava.security.manager=disallow` may appear in `pljava.vmoptions`. Default is policy-enforcing (same as `allow`) if neither appears.| +|18–23|One of `-Djava.security.manager=allow` or `-Djava.security.manager=disallow` must appear in `pljava.vmoptions`, or PL/Java will fail to start. There is no default.| +|24–|`-Djava.security.manager=disallow` must appear in `pljava.vmoptions`, or PL/Java will fail to start.| +[Allowed `java.security.manager` settings by Java version] + +When `pljava.libjvm_location` points to a Java 17 or earlier JVM, there is +no special VM option needed, and PL/Java will operate with policy enforcement +by default. However, when `pljava.libjvm_location` points to a Java 18 or later +JVM, `pljava.vmoptions` must contain either `-Djava.security.manager=allow` or +`-Djava.security.manager=disallow`, to select operation with or without policy +enforcement, respectively. No setting other than `allow` or `disallow` will +work. Only `disallow` is available for stock Java 24 or later. + +The behavior with `allow` (and the default before Java 18) is further described +in [Configuring permissions in PL/Java][policy]. + +The behavior with `disallow`, the only mode offered for Java 24 and later, +is detailed in [PL/Java with no policy enforcement][unenforced], which +should be carefully reviewed when PL/Java will be used in this mode. + +[variables]: ../use/variables.html +[policy]: ../use/policy.html +[unenforced]: ../use/unenforced.html diff --git a/src/site/markdown/install/vmoptions.md b/src/site/markdown/install/vmoptions.md index 563744aaf..674cb9fa0 100644 --- a/src/site/markdown/install/vmoptions.md +++ b/src/site/markdown/install/vmoptions.md @@ -21,11 +21,15 @@ JVM, `pljava.vmoptions` must contain either `-Djava.security.manager=allow` or enforcement, respectively. No setting other than `allow` or `disallow` will work. Only `disallow` is available for stock Java 24 or later. +For just how to configure specific Java versions, see +[Available policy-enforcement settings by Java version][smprop]. + Before operating with `disallow`, the implications detailed in [PL/Java with no policy enforcement][unenforced] should be carefully reviewed. [policy]: ../use/policy.html [unenforced]: ../use/unenforced.html +[smprop]: smproperty.html ## Adding to the set of readable modules diff --git a/src/site/markdown/use/policy.md b/src/site/markdown/use/policy.md index dfdc905b5..e71c46408 100644 --- a/src/site/markdown/use/policy.md +++ b/src/site/markdown/use/policy.md @@ -9,7 +9,8 @@ When using PL/Java with stock Java 24 or later, please see instead the To operate with policy enforcement as described here, no special configuration is needed on Java 17 and earlier, while on Java 18 through 23, an entry `-Djava.security.manager=allow` in [`pljava.vmoptions`][confvar] must be present -for PL/Java to start. +for PL/Java to start. For just how to configure specific Java versions, see +[Available policy-enforcement settings by Java version][smprop]. ## `TRUSTED` (and untrusted) procedural languages @@ -403,3 +404,4 @@ For details on how PL/Java will adapt, please bookmark [trial]: trial.html [unenforced]: unenforced.html [jep411]: https://github.com/tada/pljava/wiki/JEP-411 +[smprop]: ../install/smproperty.html diff --git a/src/site/markdown/use/unenforced.md b/src/site/markdown/use/unenforced.md index 5a36bc8f5..1cec7493f 100644 --- a/src/site/markdown/use/unenforced.md +++ b/src/site/markdown/use/unenforced.md @@ -67,6 +67,9 @@ The string `-Djava.security.manager=disallow` must appear in the setting of [`pljava.vmoptions`][vmoptions] or PL/Java will be unable to start on Java 24 or later. +For details on what `java.security.manager` settings to use on other Java +versions, see [Available policy-enforcement settings by Java version][smprop]. + ### in `pljava.allow_unenforced` Typically, a PL extension that provides only 'untrusted' execution will define @@ -240,3 +243,4 @@ modification would otherwise result). [mappedudt]: ../pljava-api/apidocs/org.postgresql.pljava/org/postgresql/pljava/annotation/MappedUDT.html [examples]: ../examples/examples.html [java_modules]: ../pljava-examples/apidocs/index.html?org/postgresql/pljava/example/annotation/Modules.html +[smprop]: ../install/smproperty.html diff --git a/src/site/markdown/use/variables.md b/src/site/markdown/use/variables.md index 7f2878ee3..87be1205a 100644 --- a/src/site/markdown/use/variables.md +++ b/src/site/markdown/use/variables.md @@ -200,7 +200,8 @@ These PostgreSQL configuration variables can influence PL/Java's operation: setting, determining whether PL/Java will run [with security policy enforcement][policy] or [with no policy enforcement][unenforced], and those pages should be reviewed - for the implications of the choice. + for the implications of the choice. Details vary by Java version; see + [Available policy-enforcement settings by Java version][smprop]. [pre92]: ../install/prepg92.html [depdesc]: https://github.com/tada/pljava/wiki/Sql-deployment-descriptor @@ -215,3 +216,4 @@ These PostgreSQL configuration variables can influence PL/Java's operation: [policy]: policy.html [unenforced]: unenforced.html [mappedudt]: ../pljava-api/apidocs/org.postgresql.pljava/org/postgresql/pljava/annotation/MappedUDT.html +[smprop]: ../install/smproperty.html From d28e9faf916e2edbb941eaf0f67fc942224cfa52 Mon Sep 17 00:00:00 2001 From: Chapman Flack Date: Mon, 3 Mar 2025 16:29:34 -0500 Subject: [PATCH 12/12] Mention how to see Java's module resolution log --- src/site/markdown/use/jpms.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/site/markdown/use/jpms.md b/src/site/markdown/use/jpms.md index 848e8c809..300b84121 100644 --- a/src/site/markdown/use/jpms.md +++ b/src/site/markdown/use/jpms.md @@ -117,6 +117,14 @@ The supplied [examples jar][examples] provides a function, [java_modules][], that can be used to see what modules have been resolved into Java's boot module layer. +For more detail on why the boot layer includes the modules it does, +`-Djdk.module.showModuleResolution=true` can be added temporarily in +`pljava.vmoptions`, and a log of module requirements and bindings will be sent +to the standard output of the backend process when PL/Java starts. PostgreSQL, +however, may normally start backend processes with standard output going +nowhere, so the logged information may be invisible unless running PostgreSQL +in [a test harness][node]. + ## Configuring the launch-time module path The configuration variable `pljava.module_path` controls the @@ -153,3 +161,4 @@ character. [unenforced]: unenforced.html [examples]: ../examples/examples.html [java_modules]: ../pljava-examples/apidocs/index.html?org/postgresql/pljava/example/annotation/Modules.html +[node]: ../develop/node.html