load classes without initializing them in Converter.CLASS#432
Conversation
|
@farkhalit |
|
Because resolving a class name currently executes code from that class.
Repro against master: public class Evil {
static { System.out.println("STATIC INITIALIZER RAN"); }
public Evil() {}
}
public class Repro {
public static void main(String[] a) throws Exception {
Options o = PatternOptionBuilder.parsePattern("c+");
CommandLine cl = new DefaultParser().parse(o, new String[] {"-c", "Evil"});
System.out.println("before getParsedOptionValue");
Class<?> k = cl.getParsedOptionValue("c");
System.out.println("got class object: " + k.getName());
}
}
With the three-arg overload and Happy to drop it if you consider running argv-named static initializers at parse time acceptable, but it looked wrong to me. |
|
Hello @farkhalit |
|
Fair question. It is not a security boundary on its own: if someone can already control argv and put a class on your classpath, they have other options. What it changes is ordering, and that is what an application can actually use. The sane way to consume a Class<?> c = cl.getParsedOptionValue("c");
if (!Plugin.class.isAssignableFrom(c)) {
throw new ParseException("not a plugin: " + c.getName());
}
Plugin p = (Plugin) c.getConstructor().newInstance();On master that check is dead code from a protection standpoint. The static initializer of whatever name appeared in argv has already run inside Secondary, more mundane benefit: eager initialization means a class whose static init is heavy or throws surfaces as
If you would rather keep the current semantics, happy to close it. |
(...)
You keep stating that as fact, but you don't really back that up with motivation. The advantages of doing the initialization early (the current behavior) are:
The advantages of doing the initialization lazily (this PR) are:
I think I like the these advantages, but it's not obvious that it's better, right? |
|
@farkhalit |
|
@farkhalit ping 🔔 |
Converter.CLASSwasClass::forName, the single-argument overload, which initializes the class it resolves. Any option declared with typeClassreaches it, so-c some.Klassruns that class's static initializer during parsing, before the application has looked at the returnedClassobject at all. Resolving a name should not execute code.The three-argument overload with
initializeset tofalsegives the same defining class loader and the sameClassNotFoundExceptioncontract, and defers initialization to first real use.Converter.OBJECTis unaffected becausegetConstructor().newInstance()initializes anyway.Reachable through
PatternOptionBuilderpattern+,TypeHandler.createClass, andCommandLine.getParsedOptionValue; the new test fails on master and passes with the change.mvn; that'smvnon the command line by itself.