Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replacing SecurityManager with a tracking agent #3386

Open
wants to merge 44 commits into
base: master
Choose a base branch
from

Conversation

jlahoda
Copy link
Contributor

@jlahoda jlahoda commented Dec 26, 2021

This is a prototype of an attempt to replace the deprecated SecurityManager with a custom agent-based hooks.

What functionality is kept:
-preventing inadvertent System.exit
-tracking file I/O (probably the most complex part, might be missing some hooks)
-checking for deprecated system property access
-checking calls to setAccessible to open Unsafe (this didn't work on JDK 8+, but can be fixed)
-checking/preventing calls to System.setSecurityManager. Possibly no longer that critical, as it is not possible to disable the hooks using this method.
-tracking new Window creation (used by internal execution)

What functionality is not kept:
-checkConnect for connecting over network. This didn't seem to be actually used, as the original code was:

        try {
            checkPermission(allPermission);
            return;
        } catch (SecurityException e) {
        }
//proceed with the actual check

But checkPermission seems to always(?) pass, so the check was never performed
-checkLogger, which was disabled anyway
-tweaks related to installation of a custom Swing clipboard - these appear to exists for JDK-4818143, which is marked as fixed, so presumably not needed anymore.

When looking at the patch, TrackingAgent is the agent class that does various transformations to install hooks, TrackingHooks are the callback, TopSecurityManager (o.n.bootstrap), FileChangedManager (masterfs), SecMan (core.execution) are the actual uses of the hooks.

@mbien
Copy link
Member

mbien commented Dec 27, 2021

this is pretty cool!

Copy link

@JaroslavTulach JaroslavTulach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magnificent work.

I'd prefer if the code was packaged as NetBeans module for ease of tooling as well as packaging and deploying. With proper Javadoc it could even be used as a great standalone library.

Inviting @sdedic as a reviewer. I'd say Sváťa is long time NetBeans expert when it comes to bytecode patching.

@@ -192,7 +192,7 @@ fi
# rename old heap dump to .old
mv "${userdir}/var/log/heapdump.hprof" "${userdir}/var/log/heapdump.hprof.old" > /dev/null 2>&1

jargs_without_clusters="$jargs -Djava.security.manager=allow"
jargs_without_clusters="$jargs -Djava.security.manager=allow -javaagent:${plathome}/core/tracking-agent.jar -Xbootclasspath/a:${plathome}/core/tracking-hooks.jar"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default all JARs in core directory are added to fixed modules classpath. Does this mean we have to tracking-agent.jar used three times?

  • put on bootclasspath
  • used as javaagent
  • added on fixed module classpath

@@ -558,27 +499,17 @@ meth public abstract void flushCaches(java.io.DataOutputStream) throws java.io.I

CLSS public org.netbeans.TopSecurityManager

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the TopSecurityManager class and replacing its superclass is one option. I'd like to ask @sdedic for his opinion: there is a support for patching classes and even for changing superclass - possibly we could use it here? If we want to be really compatible? Do we want to?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given this is a friend API, I don't see such a strong need to keep compatibility, esp. in an aspect that will cease to work at some point.

@@ -26,3 +26,5 @@ test.qa-functional.cp.extra=${openide.nodes.dir}/modules/org-openide-nodes.jar:\
test.config.stable.includes=**/hgStableTest.class
# #178009
disable.qa-functional.tests=true

test.unit.cp.extra=${nb_all}/platform/o.n.bootstrap/netbeans-tracking-agent/tracking-hooks/dist/tracking-hooks.jar

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than using test.unit.cp.extra I'd prefer making tracing-hooks real module of the platform. Then we can:

  • have test/regular dependencies when using it
  • packaging into NBM & Maven repository is straighforward

Is there any downside of having the functionality as a NetBeans module?

@@ -88,7 +89,7 @@
private static final Logger LOG = Logger.getLogger(Install.class.getName());

public @Override void run() {
TopSecurityManager.register(SecMan.DEFAULT);
TrackingHooks.register(SecMan.DEFAULT, 100, TrackingHooks.HOOK_EXIT, TrackingHooks.HOOK_NEW_AWT_WINDOW);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to have declarative registration of the TracingHooks? E.g. Lookup.getDefault() or ServiceLoader.load instead of register method?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems fairly complex, as there are uses that actively use the pre-existing register/unregister mechanism in TopSecurityManager:

TopSecurityManager.register(SecMan.DEFAULT);

TopSecurityManager.unregister(SecMan.DEFAULT);

It is not clear to me how to replace that with a Lookup-based registration. But if you can provide a patch to resolve all existing uses of TopSecurityManager.register/unregister, then we can switch to a Lookup-based registration.

File wd = getWorkDir();
File classes = new File(wd, "classes");
classes.mkdirs();
CompilationTask compilation = ToolProvider.getSystemJavaCompiler().getTask(null, null, null, Arrays.asList("-classpath", CloseTest.class.getProtectionDomain().getCodeSource().getLocation().getPath(), "-d", classes.getAbsolutePath()), null, Arrays.asList(new SimpleJavaFileObject(new URI("mem://Test.java"), Kind.SOURCE) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What a test!

@@ -38,4 +40,13 @@
<compilerarg line="${javac.compilerargs}"/>
</javac>
</target>
<target name="-build-hooks">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to put the tracing agent/hooks into its own module.

@@ -0,0 +1,1789 @@
<?xml version="1.0" encoding="UTF-8"?>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoiding old Ant projects in NetBeans code base would make me happier.

additionalConstantPool.write(data);
}

int maxStack = readShort(classfileBuffer, pp); pp += 2;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Patching classfile manually by hand! @sdedic has already introduced asm.jar into the platform. It'd be good to consider some synergy among this and his code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On one hand, it would be easier to use ASM. On the other hand, ASM cannot handle new classfiles - we could workaround that by downgrading the versions of the classfiles sent to ASM; it would also mean to place ASM into the agent path, or doing some trampoline from the agent to a normal module. I guess I'd try with the handcoded bytecode patcher, and we'll see what are the experiences with that.


protected void checkFileWrite(String path) {}
protected void checkFileRead(String path) {}
protected void checkDelete(String path) {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is mixing API and SPI. Wouldn't it be cleaner to separate the two concepts into own files?

<available file="${nbplatform.active.dir}/platform/core" type="dir" />
</condition>
<property name="test.tracking.agent.dir" location="${o.n.bootstrap.dir}/core/" />
<property name="test.tracking.agent.args" value="-javaagent:${test.tracking.agent.dir}/tracking-agent.jar -Xbootclasspath/a:${test.tracking.agent.dir}/tracking-hooks.jar"/>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having to manually modify -Xbootclasspath is annoying. Can't agent modify classpath programatically? There are methods like appendToBootstrap....

@jlahoda
Copy link
Contributor Author

jlahoda commented Aug 17, 2022

I wonder if there are any further comments on this. It might be reasonable to try to avoid too much dependency on the SecurityManager.

@matthiasblaesing
Copy link
Contributor

Given that we are early in the NB16 cycle, now would be a good time to merge this if ready.

@@ -232,25 +232,27 @@ public void testAccessClassPathDefinedAutoload() {
public void testModulesForCL() throws Exception {
Set<String> s = NbModuleSuite.S.findEnabledModules(ClassLoader.getSystemClassLoader());
s.remove("org.netbeans.modules.nbjunit");
assertEquals("Four modules left: " + s, 5, s.size());
assertEquals("Four modules left: " + s, 6, s.size());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

four > six ?

@@ -88,7 +88,7 @@ public class Install implements Runnable {
private static final Logger LOG = Logger.getLogger(Install.class.getName());

public @Override void run() {
TopSecurityManager.register(SecMan.DEFAULT);
TrackingHooks.register(SecMan.DEFAULT, 100, TrackingHooks.Hooks.EXIT, Hooks.NEW_AWT_WINDOW);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: TrackingHooks. redundant.

# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
javac.source=1.8
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

autoload ?

for (Iterator<HookDescription> it = delegates.iterator(); it.hasNext();) {
HookDescription hd = it.next();
if (hd.hooks == toRemove) {
it.remove();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stop after first removed ?

return classfileBuffer;
}
try {
List<MethodEnhancement> thisClassEnhancements = toInject.stream().filter(me -> {/*System.err.println("className=" + className); */return className.equals(me.className);}).collect(Collectors.toList());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to organize toInject as a Map keyed by FQN ? It would allow faster lookup here. Also not sure about effectivness of the stream filter approach for that small number of items.

constantPool.add(null);
break;
default:
System.err.println("unknown constant pool tag: " + tag);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Logger.

System.arraycopy(classfileBuffer, lastCopySource, newBuffer, lastCopyDest, len); lastCopySource += len; lastCopyDest += len;
return newBuffer;
} catch (Throwable t) {
t.printStackTrace();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use logger; it is really useful to catch all Throwables ?

Class<?> stackWalker = Class.forName("java.lang.StackWalker");
Class<?> stackWalkerOption = Class.forName("java.lang.StackWalker$Option");
Class<?> stackWalkerStackFrame = Class.forName("java.lang.StackWalker$StackFrame");
Method getInstance = stackWalker.getDeclaredMethod("getInstance", stackWalkerOption);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this reflective lookups could be done just once ?

@@ -67,9 +67,6 @@ public static void advancePolicy() {
if (!Boolean.getBoolean("TopSecurityManager.disable")) {
// set security manager
TopSecurityManager.install();
if (CLIOptions.isGui()) {
TopSecurityManager.makeSwingUseSpecialClipboard(Lookup.getDefault().lookup(ExClipboard.class));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was this supposed to do ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this was part of a workaround for JDK-4818143. That bug is marked as fixed in JDK 6. So, it seems that keeping the workaround is probably not useful anymore (and, in case the problem would not be actually fixed, this is a way to find out).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't that also stop the system clipboard being an instance of ExClipboard? If so, seem a bunch of places that expect it to be one.

@mbien mbien added Java [ci] enable extra Java tests (java.completion, java.source.base, java.hints, refactoring.java, form) Platform [ci] enable platform tests (platform/*) ci:all-tests [ci] enable all tests labels Oct 26, 2022
@mbien
Copy link
Member

mbien commented Oct 28, 2022

all green. excellent!

@mbien
Copy link
Member

mbien commented May 9, 2023

JEP 451: Prepare to Disallow the Dynamic Loading of Agents
will require the -XX:+EnableDynamicAgentLoading flag (sooner or later, will print warnings first).

It might be better to load the agent using JVM flags on startup (instead of dynamically), since there are probably good reasons JEPs like that exist.

@neilcsmith-net
Copy link
Member

@mbien isn't it already doing this - https://github.com/apache/netbeans/pull/3386/files#diff-d4b605d5fdbc82d1afa51dc1d6c3ce44516cf46c5e2ac0c79a98118bdcefb896R195

Or are there other uses in there? I assume we'll need a Windows launcher update too? Wish that could be a script too!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ci:all-tests [ci] enable all tests Java [ci] enable extra Java tests (java.completion, java.source.base, java.hints, refactoring.java, form) Platform [ci] enable platform tests (platform/*)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants