Skip to content

Conversation

@jcflack
Copy link
Contributor

@jcflack jcflack commented Feb 24, 2025

Java 24, with JEP 486, completes the process, begun with JEP 411, of completely gutting Java's ability to enforce security policy, heavily relied on by PL/Java.

With this PR, PL/Java can still be used with all of its familiar policy-enforcement features, as long as pljava.libjvm_location points to a Java 23 or earlier JVM, and can also be used—with explicit variable settings to deliberately opt in—with no policy enforcement at all. Only this 'unenforced' mode is available when pljava.libjvm_location points to a Java 24 or later JVM.

This, at least, gives a DBA a choice. If a 'trusted' PL with policy enforcement is more important than the very latest Java language features, continue to use PL/Java with a Java 23 or earlier JVM, such as the long-term-support Java 21 release. On the other hand, if the latest Java features are of interest and an 'untrusted' PL with few guardrails is acceptable, the same PL/Java installation can be used in that mode with a Java 24 or later JVM.

It is possible to switch modes at will, simply by changing the JVM that pljava.libjvm_location points to, and a few GUCs.

Because of that flexibility, PL/Java continues to create two PL entries, one called javau and the other called java and marked TRUSTED, even when running in unenforced mode. There may be functions already declared of both kinds carried over from earlier, and the distinction may be enforced again simply by pointing PL/Java at a Java 23 or earlier JVM again.

However, in unenforced mode, execution is equally unrestricted regardless of the PL name used or its TRUSTED designation. Also, in unenforced mode, only database superusers may create functions, even when the PL is marked TRUSTED, regardless of any grant of USAGE on the PL.

Opting in

It would clearly be inappropriate for PL/Java to silently switch to unenforced mode just because the JVM has been updated from a pre-24 to a 24-or-later version. An admin needs to think deliberately about the implications, and perhaps audit some existing Java code, before deciding whether to use a 24-or-later JVM and opt in to unenforced execution.

-Djava.security.manager=...

The primary choice of mode is whether -Djava.security.manager=allow (for the familiar, policy-enforcing mode) or -Djava.security.manager=disallow (for unenforced mode) appears in the pljava.vmoptions setting:

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.

However, simply starting PL/Java with -Djava.security.manager=disallow by itself will not allow any Java functions, or Java-based user-defined-type data conversion methods, to execute in unenforced mode. Those must be further approved, as follows.

pljava.allow_unenforced=PL name,...

This GUC is a list of PL names, such as javau and java (there can be more, as PL./Java's policy-enforcing mode has allowed additional names to be created with SQLJ.ALIAS_JAVA_LANGUAGE and associated with differently-tailored policies).

No PL/Java function will be allowed to execute in unenforced mode unless the PL name in which it is declared appears in this list.

The list is empty by default. Not even javau—which should be the easiest call, as that PL was already considered untrusted—is allowed until added to this list by the admin. That is because even javau was subject to a security policy, albeit a more relaxed one, in policy-enforcing mode. In unenforced mode, even those guardrails are removed.

The per-PL-name granularity of this setting allows an admin to divide the work of auditing existing Java code, and add each PL to this list after the functions declared in it have been reviewed.

CREATE FUNCTION in a PL that is not named here will usually be reported as an error in unenforced mode, too. It won't be if check_function_bodies is off, though, so this should be seen more as a friendly reminder than as a form of security. If the CREATE FUNCTION succeeds, the function still cannot execute until the PL name is added to this list.

pljava.allow_unenforced_udt=on/off

The pljava.allow_unenforced setting based on PL names will not affect the data-conversion readSQL/writeSQL Java methods of PL/Java-based mapped user-defined types, because those do not have corresponding SQL declarations to associate a PL name. The on/off setting of pljava.allow_unenforced_udt controls the execution of all such UDT methods. Its default setting is off, so an admin can change it to on after review of any affected Java code.

Hardening

New documentation on the unenforced mode includes hardening advice, essentially to milk every remaining Java integrity-supporting feature for all it's worth. Suggestions include the --limit-modules VM option to reduce the number of Java system modules visible to user code, and using the --sun-misc-unsafe-memory-access=deny and --illegal-native-access=deny VM options whenever the JVM version in use recognizes them. (Even in policy-enforcing mode, these techniques can still be recommended as part of a defense-in-depth approach.)

All Java system properties are writable in unenforced mode, so defensive early copying is recommended. There are many system properties that are often assumed to convey reliable information about the environment. PL/Java itself now creates an unmodifiable early copy of the system properties, before user code has executed, and makes it available to user code through a new method on the Session object.

PL/Java does not yet make it convenient to deploy user code as named modules (the SQL/JRT standard predated Java's module system), but it is possible, and can be advantageous if only a subset of the code requires some special treatment. Per-module VM options now seem to be the finest granularity of permission the developers of Java intend to support.

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.
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.
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.
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".)
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.
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.
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.
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.
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.
The table from the pull request #512 comment would be useful
to have in the actual docs.
@jcflack jcflack merged commit ea0977f into REL1_6_STABLE Mar 5, 2025
12 checks passed
jcflack added a commit that referenced this pull request Mar 10, 2025
A couple documentation changes and a diagnostic message improved,
overlooked in PR #512 (d28e9fa). Pushing these without another PR.

The Java-version condition determining the exact message shown when
beginEnforcing fails is now harmonized with what install/smproperty.md
says about use of the java.security.manager property by Java version.
The property must be supplied starting with Java 18, so the useful hint
should be given for failures on 18 through 23, rather than calling
the failure unexpected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants