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

The one-argument Class.forName must never be inlined #6789

Open
scabug opened this issue Dec 9, 2012 · 5 comments
Open

The one-argument Class.forName must never be inlined #6789

scabug opened this issue Dec 9, 2012 · 5 comments
Milestone

Comments

@scabug
Copy link

scabug commented Dec 9, 2012

See my comment in #5457. Inlining Class.forName across class boundaries will cause the wrong classloader to be used if the caller and callee were loaded from different classloaders. If the call is first expanded to the three argument form, inlining should be safe (because the call to this.getClass.getClassLoader is explicit rather than being performed by the jvm.)

This issue may very well apply to other classloader-sensitive methods as well.

Here's the skeleton of a test to document the issue. g1 and g2 should not display different behavior regardless of where classes are loaded from.

final class A {
  @inline def f1(name: String) = Class.forName(name, true, this.getClass.getClassLoader)
  @inline def f2(name: String) = Class.forName(name)
}

class B {
  def g1(name: String) = (new A) f1 "Bippy"
  def g2(name: String) = (new A) f2 "Bippy"
}
@scabug
Copy link
Author

scabug commented Dec 9, 2012

Imported From: https://issues.scala-lang.org/browse/SI-6789?orig=1
Reporter: @paulp

@scabug
Copy link
Author

scabug commented Jan 13, 2013

@scabug
Copy link
Author

scabug commented Jan 14, 2013

@retronym said:
The complete list of methods that should prevent inlining as actually pretty large.

First of all, we need to find all the places where getCallerClassLoader is used.

That gives us a bunch of methods in j.l.Class, and a few in j.l.ClassLoader, j.l.Package, j.l.Thread, j.sql.DriverManager, javax.script.ScriptEngineManager.

But then there are all the calls to j.l.SecurityManager which indirectly call j.security.AccessController.getStackAccessControlContext. Suffice to safe that is a very long list. And even if we were to catalog it and avoid inlining methods that directly call them, it isn't enough -- we still might eliminated a frame that was granted some permission needed later down the call chain.

Of course, this level of paranoia would also cause us to worry about changing the behaviour of Thread.getCurrentThread.getStackTrace, but that is probably a bridge too far. (Although, most logging libraries use this to find the caller's class name: http://logback.qos.ch/xref/ch/qos/logback/classic/spi/CallerData.html).

I think that it is worthwhile to add protection for the known callers to getCallerClassLoader, and I'll submit a PR to that effect. I think I'll forbid calls to any method in those classes, rather than picking out the methods one-by-one.

But as to security, I think we'll have to document it as a caveat of compiling with -optimize. I'm sure that 99% of users will be unaffected by it.

@scabug
Copy link
Author

scabug commented Jan 14, 2013

@retronym said:
Here's a completed patch:

retronym/scala@scala:2.10.x...retronym:ticket/6789

I'm still feeling a bit dirty about this, I'd like some feedback before proposing it as a pull request.

@scabug
Copy link
Author

scabug commented Jan 15, 2013

@retronym said (edited on Jan 15, 2013 8:18:10 AM UTC):
More food for thought:

Your message prompts a much more basic question (which could allow a much simpler fix, namely blacklisting the whole Java class library): how can we ever inline JDK's code while keeping it compatible with changes in the standard Java library? We inline Java method X from JDK library Y, that method's fixed in next release of the JDK, but since the application is not recompiled the fix is not included.

This can have security implication; If we assume that the fix is for a security bug, a Scala application is potentially affected if any Scala code it uses (libraries or the application itself) was compiled with -optimise on a vulnerable machine.
Please tell me I'm missing something here. Please.

Such problems potentially generalize to any cross-library inlining, but only if the library contained the inlined code is allowed to be upgraded - is that on by default in when you create a POM file? I have no clue on publishing libraries.
Also, this gets fun when you inline code from a buggy cryptography library, for instance, or (for different reasons) when you inline enough code to depend on changing implementation details of a library (not sure if that's possible in Scala though).

-Paolo

Thanks for raising this.

Another variation thereof: inline a method body from the compile time JDK, which refers to classes/methods that are not available on some alternative runtime JDK.

A reasonable heuristic to approximate disabling 'cross library inlining' might be to only allow inlining from code that is provided to the compiler as source files or as .class files, but not as JAR files. Inlining of methods in the Scala standard library has all the same implications as the Java standard library, however if you disable that you lose an important class of performance wins.

We have to think very carefully about defaults here, and find a way balance the needs of the 'closed world' users who want maximum performance vs the 'open world' users who need safety in the face of unknown runtime environments.

-jason

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant