From 12cdc2182b3bb33481ba1a954dfffbc9b1c3690f Mon Sep 17 00:00:00 2001 From: nickl- Date: Wed, 25 Oct 2017 20:40:04 +0200 Subject: Make an effort to secure privileged use. Oracle (or maybe it was still Sun) warns that we should prevent from exposing access we gain to privileged functionality like the unsafe etc. Before Oracle decides to restrict us even more lets make an effort at least. --- .../javassist/util/proxy/DefineClassHelper.java | 158 +++++++++++---------- 1 file changed, 82 insertions(+), 76 deletions(-) diff --git a/src/main/javassist/util/proxy/DefineClassHelper.java b/src/main/javassist/util/proxy/DefineClassHelper.java index 97c83800..5599316e 100644 --- a/src/main/javassist/util/proxy/DefineClassHelper.java +++ b/src/main/javassist/util/proxy/DefineClassHelper.java @@ -16,13 +16,15 @@ package javassist.util.proxy; -import java.io.IOException; import java.lang.reflect.Method; import java.lang.reflect.Field; import java.security.ProtectionDomain; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import sun.misc.Unsafe; +import java.security.AccessController; +import java.security.PrivilegedAction; import javassist.CannotCompileException; import javassist.bytecode.ClassFile; @@ -33,37 +35,78 @@ import javassist.bytecode.ClassFile; * @since 3.22 */ public class DefineClassHelper { - private static java.lang.reflect.Method defineClass1 = null; - private static java.lang.reflect.Method defineClass2 = null; - private static Unsafe sunMiscUnsafe = null; - - static { - if (ClassFile.MAJOR_VERSION < ClassFile.JAVA_9) - try { - Class cl = Class.forName("java.lang.ClassLoader"); - defineClass1 = SecurityActions.getDeclaredMethod( - cl, - "defineClass", - new Class[] { String.class, byte[].class, - int.class, int.class }); + private static enum SecuredPrivileged { + JAVA_9 { + final class SecuredUnsafe { + private final sun.misc.Unsafe theUnsafe; - defineClass2 = SecurityActions.getDeclaredMethod( - cl, - "defineClass", - new Class[] { String.class, byte[].class, - int.class, int.class, ProtectionDomain.class }); + SecuredUnsafe(sun.misc.Unsafe usf) { + this.theUnsafe = usf; + } + + @SuppressWarnings("deprecation") + Class defineClass(String name, byte[] b, int off, int len, + ClassLoader loader, ProtectionDomain protectionDomain) { + return theUnsafe.defineClass(name, b, off, len, loader, protectionDomain); + } } - catch (Exception e) { - throw new RuntimeException("cannot initialize"); + + private final SecuredUnsafe sunMiscUnsafe = AccessController.doPrivileged( + new PrivilegedAction() { public SecuredUnsafe run() { + try { + Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + sun.misc.Unsafe usf = (sun.misc.Unsafe)theUnsafe.get(null); + return new SecuredUnsafe(usf); + } + catch (Throwable t) {} + return null; + } + }); + + @Override + public Class defineClass(String name, byte[] b, int off, int len, + ClassLoader loader, ProtectionDomain protectionDomain) throws ClassFormatError { + return sunMiscUnsafe.defineClass(name, b, off, len, loader, protectionDomain); } - else - try { - Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); - theUnsafe.setAccessible(true); - sunMiscUnsafe = (sun.misc.Unsafe)theUnsafe.get(null); + }, + JAVA_OTHER { + private final MethodHandle defineClass = AccessController.doPrivileged( + new PrivilegedAction() { public MethodHandle run() { + try { + Method rmet = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] { + String.class, byte[].class, int.class, int.class, ProtectionDomain.class + }); + rmet.setAccessible(true); + MethodHandle meth = MethodHandles.lookup().unreflect(rmet); + rmet.setAccessible(false); + return meth; + } catch (Throwable t) {}; + return null; + } + }); + + @Override + public Class defineClass(String name, byte[] b, int off, int len, + ClassLoader loader, ProtectionDomain protectionDomain) throws ClassFormatError { + try { + return (Class) defineClass.invokeWithArguments(loader, name, b, off, len, protectionDomain); + } catch (Throwable e) { + if (e instanceof ClassFormatError) throw (ClassFormatError) e; + if (e instanceof RuntimeException) throw (RuntimeException) e; + } + return null; } - catch (Throwable t) {} + }; + + public abstract Class defineClass(String name, byte[] b, int off, int len, + ClassLoader loader, ProtectionDomain protectionDomain) throws ClassFormatError; + + } + private static final SecuredPrivileged privileged = ClassFile.MAJOR_VERSION < ClassFile.JAVA_9 + ? SecuredPrivileged.JAVA_OTHER + : SecuredPrivileged.JAVA_9; /** * Loads a class file by a given class loader. @@ -84,15 +127,18 @@ public class DefineClassHelper { ProtectionDomain domain, byte[] bcode) throws CannotCompileException { - if (ClassFile.MAJOR_VERSION >= ClassFile.JAVA_9) - if (sunMiscUnsafe != null) - try { - return sunMiscUnsafe.defineClass(className, bcode, 0, bcode.length, - loader, domain); - } - catch (Throwable t2) {} - - return toClass2(className, loader, domain, bcode); + try { + return privileged.defineClass(className, bcode, 0, bcode.length, loader, domain); + } + catch (RuntimeException e) { + throw e; + } + catch (ClassFormatError e) { + throw new CannotCompileException(e.getCause()); + } + catch (Exception e) { + throw new CannotCompileException(e); + } } /** @@ -113,44 +159,4 @@ public class DefineClassHelper { } } - private static Class toClass2(String cname, ClassLoader loader, - ProtectionDomain domain, byte[] bcode) - throws CannotCompileException - { - try { - Method method; - Object[] args; - if (domain == null) { - method = defineClass1; - args = new Object[] { cname, bcode, Integer.valueOf(0), - Integer.valueOf(bcode.length) }; - } - else { - method = defineClass2; - args = new Object[] { cname, bcode, Integer.valueOf(0), - Integer.valueOf(bcode.length), domain }; - } - - return toClass3(method, loader, args); - } - catch (RuntimeException e) { - throw e; - } - catch (java.lang.reflect.InvocationTargetException e) { - throw new CannotCompileException(e.getTargetException()); - } - catch (Exception e) { - throw new CannotCompileException(e); - } - } - - private static synchronized - Class toClass3(Method method, ClassLoader loader, Object[] args) - throws Exception - { - SecurityActions.setAccessible(method, true); - Class clazz = (Class)method.invoke(loader, args); - SecurityActions.setAccessible(method, false); - return clazz; - } } -- cgit v1.2.3 From 31b7faa0bc15d62d5c390096a02cebed344a620d Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 06:43:49 +0200 Subject: Add getMethodHandle to SecurityActions. The main advantage is that we can do the privileged setAccessible during creation and then freely invoke via the authorized method handle. As per the javadocs: Access checks are applied in the factory methods of Lookup, when a method handle is created. his is a key difference from the Core Reflection API, since java.lang.reflect.Method.invoke performs access checking against every caller, on every call. The performance boost is just a bonus. --- src/main/javassist/util/proxy/SecurityActions.java | 30 ++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/javassist/util/proxy/SecurityActions.java b/src/main/javassist/util/proxy/SecurityActions.java index ed1ec981..272ccfec 100755 --- a/src/main/javassist/util/proxy/SecurityActions.java +++ b/src/main/javassist/util/proxy/SecurityActions.java @@ -15,6 +15,8 @@ */ package javassist.util.proxy; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -51,8 +53,32 @@ class SecurityActions { } } - static Method getDeclaredMethod(final Class clazz, final String name, - final Class[] types) throws NoSuchMethodException { + static MethodHandle getMethodHandle(final Class clazz, final + String name, final Class[] params) throws NoSuchMethodException + { + try { + return AccessController.doPrivileged( + new PrivilegedExceptionAction() { + public MethodHandle run() throws IllegalAccessException, + NoSuchMethodException, SecurityException { + Method rmet = clazz.getDeclaredMethod(name, params); + rmet.setAccessible(true); + MethodHandle meth = MethodHandles.lookup().unreflect(rmet); + rmet.setAccessible(false); + return meth; + } + }); + } + catch (PrivilegedActionException e) { + if (e.getCause() instanceof NoSuchMethodException) + throw (NoSuchMethodException) e.getCause(); + throw new RuntimeException(e.getCause()); + } + } + + static Method getDeclaredMethod(final Class clazz, final String name, + final Class[] types) throws NoSuchMethodException + { if (System.getSecurityManager() == null) return clazz.getDeclaredMethod(name, types); else { -- cgit v1.2.3 From f52402c2410158bdf0ab53b3852d075fa5320565 Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 06:50:16 +0200 Subject: Type check and paramatized rawtypes for SecurityActions. Some whitespace got shunted around a bit too. --- src/main/javassist/util/proxy/SecurityActions.java | 79 +++++++++++----------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/src/main/javassist/util/proxy/SecurityActions.java b/src/main/javassist/util/proxy/SecurityActions.java index 272ccfec..ca0de912 100755 --- a/src/main/javassist/util/proxy/SecurityActions.java +++ b/src/main/javassist/util/proxy/SecurityActions.java @@ -27,29 +27,31 @@ import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; class SecurityActions { - static Method[] getDeclaredMethods(final Class clazz) { + static Method[] getDeclaredMethods(final Class clazz) + { if (System.getSecurityManager() == null) return clazz.getDeclaredMethods(); else { - return (Method[]) AccessController - .doPrivileged(new PrivilegedAction() { - public Object run() { - return clazz.getDeclaredMethods(); - } - }); + return AccessController.doPrivileged( + new PrivilegedAction() { + public Method[] run() { + return clazz.getDeclaredMethods(); + } + }); } } - static Constructor[] getDeclaredConstructors(final Class clazz) { + static Constructor[] getDeclaredConstructors(final Class clazz) + { if (System.getSecurityManager() == null) return clazz.getDeclaredConstructors(); else { - return (Constructor[]) AccessController - .doPrivileged(new PrivilegedAction() { - public Object run() { - return clazz.getDeclaredConstructors(); - } - }); + return AccessController.doPrivileged( + new PrivilegedAction[]>() { + public Constructor[] run() { + return clazz.getDeclaredConstructors(); + } + }); } } @@ -83,12 +85,12 @@ class SecurityActions { return clazz.getDeclaredMethod(name, types); else { try { - return (Method) AccessController - .doPrivileged(new PrivilegedExceptionAction() { - public Object run() throws Exception { - return clazz.getDeclaredMethod(name, types); - } - }); + return AccessController.doPrivileged( + new PrivilegedExceptionAction() { + public Method run() throws Exception { + return clazz.getDeclaredMethod(name, types); + } + }); } catch (PrivilegedActionException e) { if (e.getCause() instanceof NoSuchMethodException) @@ -99,20 +101,20 @@ class SecurityActions { } } - static Constructor getDeclaredConstructor(final Class clazz, - final Class[] types) + static Constructor getDeclaredConstructor(final Class clazz, + final Class[] types) throws NoSuchMethodException { if (System.getSecurityManager() == null) return clazz.getDeclaredConstructor(types); else { try { - return (Constructor) AccessController - .doPrivileged(new PrivilegedExceptionAction() { - public Object run() throws Exception { - return clazz.getDeclaredConstructor(types); - } - }); + return AccessController.doPrivileged( + new PrivilegedExceptionAction>() { + public Constructor run() throws Exception { + return clazz.getDeclaredConstructor(types); + } + }); } catch (PrivilegedActionException e) { if (e.getCause() instanceof NoSuchMethodException) @@ -124,12 +126,13 @@ class SecurityActions { } static void setAccessible(final AccessibleObject ao, - final boolean accessible) { + final boolean accessible) + { if (System.getSecurityManager() == null) ao.setAccessible(accessible); else { - AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { + AccessController.doPrivileged(new PrivilegedAction() { + public Void run() { ao.setAccessible(accessible); return null; } @@ -144,17 +147,17 @@ class SecurityActions { fld.set(target, value); else { try { - AccessController.doPrivileged(new PrivilegedExceptionAction() { - public Object run() throws Exception { - fld.set(target, value); - return null; - } - }); + AccessController.doPrivileged( + new PrivilegedExceptionAction() { + public Void run() throws Exception { + fld.set(target, value); + return null; + } + }); } catch (PrivilegedActionException e) { if (e.getCause() instanceof NoSuchMethodException) throw (IllegalAccessException) e.getCause(); - throw new RuntimeException(e.getCause()); } } -- cgit v1.2.3 From 02d023d7e53d15d4c346b70bc9a29113dc62d642 Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 06:59:33 +0200 Subject: Add sun.misc.Unsafe privileged retrieval to SecActions. Also do it anonomously so as not to raise the alarms and upset the powers to be. --- src/main/javassist/util/proxy/SecurityActions.java | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main/javassist/util/proxy/SecurityActions.java b/src/main/javassist/util/proxy/SecurityActions.java index ca0de912..ce0b5fa6 100755 --- a/src/main/javassist/util/proxy/SecurityActions.java +++ b/src/main/javassist/util/proxy/SecurityActions.java @@ -162,4 +162,33 @@ class SecurityActions { } } } + + static Object getSunMiscUnsafeAnonymously() throws ClassNotFoundException + { + try { + return AccessController.doPrivileged( + new PrivilegedExceptionAction() { public Object run() throws + ClassNotFoundException, NoSuchFieldException, SecurityException, + IllegalArgumentException, IllegalAccessException { + Class unsafe = Class.forName("sun.misc.Unsafe"); + Field theUnsafe = unsafe.getDeclaredField("theUnsafe"); + theUnsafe.setAccessible(true); + Object usf = theUnsafe.get(null); + theUnsafe.setAccessible(false); + return usf; + } + }); + } + catch (PrivilegedActionException e) { + if (e.getCause() instanceof ClassNotFoundException) + throw (ClassNotFoundException) e.getCause(); + if (e.getCause() instanceof NoSuchFieldException) + throw new ClassNotFoundException("No such instance.", e.getCause()); + if (e.getCause() instanceof IllegalAccessException + || e.getCause() instanceof IllegalAccessException + || e.getCause() instanceof SecurityException) + throw new ClassNotFoundException("Security denied access.", e.getCause()); + throw new RuntimeException(e.getCause()); + } + } } -- cgit v1.2.3 From c2ae87be4b334ae5a3d032777f2e2e526443c491 Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 07:19:22 +0200 Subject: Refactored SecActs methods and retain 1.6 source. Remembered that 1.6 won't know about MethodHandles so must use reflection methods for them still. --- .../javassist/util/proxy/DefineClassHelper.java | 118 +++++++++++++-------- 1 file changed, 73 insertions(+), 45 deletions(-) diff --git a/src/main/javassist/util/proxy/DefineClassHelper.java b/src/main/javassist/util/proxy/DefineClassHelper.java index 5599316e..ee565d80 100644 --- a/src/main/javassist/util/proxy/DefineClassHelper.java +++ b/src/main/javassist/util/proxy/DefineClassHelper.java @@ -16,15 +16,11 @@ package javassist.util.proxy; -import java.lang.reflect.Method; -import java.lang.reflect.Field; -import java.security.ProtectionDomain; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; -import sun.misc.Unsafe; -import java.security.AccessController; -import java.security.PrivilegedAction; +import java.lang.reflect.Method; +import java.security.ProtectionDomain; import javassist.CannotCompileException; import javassist.bytecode.ClassFile; @@ -34,68 +30,100 @@ import javassist.bytecode.ClassFile; * * @since 3.22 */ -public class DefineClassHelper { - private static enum SecuredPrivileged { +public class DefineClassHelper +{ + + private static enum SecuredPrivileged + { JAVA_9 { - final class SecuredUnsafe { - private final sun.misc.Unsafe theUnsafe; + final class ReferencedUnsafe + { + private final Object sunMiscUnsafeTheUnsafe; + private final MethodHandle defineClass; - SecuredUnsafe(sun.misc.Unsafe usf) { - this.theUnsafe = usf; + ReferencedUnsafe(Object usf, MethodHandle meth) + { + this.sunMiscUnsafeTheUnsafe = usf; + this.defineClass = meth; } - @SuppressWarnings("deprecation") Class defineClass(String name, byte[] b, int off, int len, - ClassLoader loader, ProtectionDomain protectionDomain) { - return theUnsafe.defineClass(name, b, off, len, loader, protectionDomain); + ClassLoader loader, ProtectionDomain protectionDomain) + throws ClassFormatError { + if (stack.getCallerClass() != SecuredPrivileged.JAVA_9.getClass()) + throw new IllegalAccessError("Access denied for caller."); + try { + return (Class) defineClass.invokeWithArguments( + sunMiscUnsafeTheUnsafe, + name, b, off, len, loader, protectionDomain); + } catch (Throwable e) { + if (e instanceof RuntimeException) throw (RuntimeException) e; + if (e instanceof ClassFormatError) throw (ClassFormatError) e; + throw new ClassFormatError(e.getMessage()); + } + } + } + private final ReferencedUnsafe sunMiscUnsafe = getReferencedUnsafe(); + private final ReferencedUnsafe getReferencedUnsafe() + { + try { + Object usf = SecurityActions.getSunMiscUnsafeAnonymously(); + MethodHandle meth = SecurityActions.getMethodHandle(ClassLoader.class, + "defineClass", new Class[] { + String.class, byte[].class, int.class, int.class, + ProtectionDomain.class + }); + return new ReferencedUnsafe(usf, meth); + } catch (Throwable e) { + throw new RuntimeException("cannot initialize", e); } } - - private final SecuredUnsafe sunMiscUnsafe = AccessController.doPrivileged( - new PrivilegedAction() { public SecuredUnsafe run() { - try { - Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); - theUnsafe.setAccessible(true); - sun.misc.Unsafe usf = (sun.misc.Unsafe)theUnsafe.get(null); - return new SecuredUnsafe(usf); - } - catch (Throwable t) {} - return null; - } - }); @Override public Class defineClass(String name, byte[] b, int off, int len, - ClassLoader loader, ProtectionDomain protectionDomain) throws ClassFormatError { - return sunMiscUnsafe.defineClass(name, b, off, len, loader, protectionDomain); + ClassLoader loader, ProtectionDomain protectionDomain) + throws ClassFormatError + { + if (stack.getCallerClass() != DefineClassHelper.class) + throw new IllegalAccessError("Access denied for caller."); + return sunMiscUnsafe.defineClass(name, b, off, len, loader, + protectionDomain); + } + }, } }, JAVA_OTHER { - private final MethodHandle defineClass = AccessController.doPrivileged( - new PrivilegedAction() { public MethodHandle run() { - try { - Method rmet = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] { + private final Method defineClass = getDefineClassMethod(); + private final Method getDefineClassMethod() { + try { + return SecurityActions.getDeclaredMethod(ClassLoader.class, + "defineClass", new Class[] { String.class, byte[].class, int.class, int.class, ProtectionDomain.class }); - rmet.setAccessible(true); - MethodHandle meth = MethodHandles.lookup().unreflect(rmet); - rmet.setAccessible(false); - return meth; - } catch (Throwable t) {}; - return null; + } catch (NoSuchMethodException e) { + throw new RuntimeException("cannot initialize", e); } - }); + } @Override - public Class defineClass(String name, byte[] b, int off, int len, - ClassLoader loader, ProtectionDomain protectionDomain) throws ClassFormatError { + protected Class defineClass(String name, byte[] b, int off, int len, + ClassLoader loader, ProtectionDomain protectionDomain) throws ClassFormatError + { + if (stack.getCallerClass() != DefineClassHelper.class) + throw new IllegalAccessError("Access denied for caller."); try { - return (Class) defineClass.invokeWithArguments(loader, name, b, off, len, protectionDomain); + SecurityActions.setAccessible(defineClass, true); + return (Class) defineClass.invoke(loader, new Object[] { + name, b, off, len, protectionDomain + }); } catch (Throwable e) { if (e instanceof ClassFormatError) throw (ClassFormatError) e; if (e instanceof RuntimeException) throw (RuntimeException) e; + throw new ClassFormatError(e.getMessage()); + } + finally { + SecurityActions.setAccessible(defineClass, false); } - return null; } }; -- cgit v1.2.3 From 7d5eb53d1241ca8bdd4624a9f5a61834bf4dc053 Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 07:36:38 +0200 Subject: MethodHandles for JDK7+. Java 7 and 8 does support method handles so we can cater for them. When doing repeated invokes to the same method, keeping a reference to the Method handle is much faster than reflection. Also the API is cleaner not having to Object[] args etc. Worth the effort... --- .../javassist/util/proxy/DefineClassHelper.java | 39 +++++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/main/javassist/util/proxy/DefineClassHelper.java b/src/main/javassist/util/proxy/DefineClassHelper.java index ee565d80..208a6f0c 100644 --- a/src/main/javassist/util/proxy/DefineClassHelper.java +++ b/src/main/javassist/util/proxy/DefineClassHelper.java @@ -90,6 +90,33 @@ public class DefineClassHelper protectionDomain); } }, + JAVA_7 { + private final MethodHandle defineClass = getDefineClassMethodHandle(); + private final MethodHandle getDefineClassMethodHandle() + { + try { + return SecurityActions.getMethodHandle(ClassLoader.class, + "defineClass", new Class[] { + String.class, byte[].class, int.class, int.class, + ProtectionDomain.class + }); + } catch (NoSuchMethodException e) { + throw new RuntimeException("cannot initialize", e); + } + } + + @Override + protected Class defineClass(String name, byte[] b, int off, int len, + ClassLoader loader, ProtectionDomain protectionDomain) throws ClassFormatError + { + try { + return (Class) defineClass.invokeWithArguments( + loader, name, b, off, len, protectionDomain); + } catch (Throwable e) { + if (e instanceof RuntimeException) throw (RuntimeException) e; + if (e instanceof ClassFormatError) throw (ClassFormatError) e; + throw new ClassFormatError(e.getMessage()); + } } }, JAVA_OTHER { @@ -125,16 +152,18 @@ public class DefineClassHelper SecurityActions.setAccessible(defineClass, false); } } + }; public abstract Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain) throws ClassFormatError; - - } - private static final SecuredPrivileged privileged = ClassFile.MAJOR_VERSION < ClassFile.JAVA_9 - ? SecuredPrivileged.JAVA_OTHER - : SecuredPrivileged.JAVA_9; + + private static final SecuredPrivileged privileged = ClassFile.MAJOR_VERSION >= ClassFile.JAVA_9 + ? SecuredPrivileged.JAVA_9 + : ClassFile.MAJOR_VERSION >= ClassFile.JAVA_7 + ? SecuredPrivileged.JAVA_7 + : SecuredPrivileged.JAVA_OTHER; /** * Loads a class file by a given class loader. -- cgit v1.2.3 From 4e81640868096321627deb064b3f63e48c9a9705 Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 07:55:08 +0200 Subject: Add caller class checking. The only thing remotely inforceable is caller class checking. You cannot bypass an exception with setAccesible. Oracle went and removed Reflection.getCallerClass() completely in favour of StackWalker. At least we can share the SecurityManage ClassContext, SecurityActions seems the appropriate venue for our stack trace peek to support the older versions --- src/main/javassist/util/proxy/DefineClassHelper.java | 15 +++++++++++++++ src/main/javassist/util/proxy/SecurityActions.java | 9 ++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/javassist/util/proxy/DefineClassHelper.java b/src/main/javassist/util/proxy/DefineClassHelper.java index 208a6f0c..b47f3b59 100644 --- a/src/main/javassist/util/proxy/DefineClassHelper.java +++ b/src/main/javassist/util/proxy/DefineClassHelper.java @@ -63,9 +63,14 @@ public class DefineClassHelper } } } + private final StackWalker stack = StackWalker + .getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); private final ReferencedUnsafe sunMiscUnsafe = getReferencedUnsafe(); private final ReferencedUnsafe getReferencedUnsafe() { + if (null != SecuredPrivileged.JAVA_9 + && stack.getCallerClass() != this.getClass()) + throw new IllegalAccessError("Access denied for caller."); try { Object usf = SecurityActions.getSunMiscUnsafeAnonymously(); MethodHandle meth = SecurityActions.getMethodHandle(ClassLoader.class, @@ -91,9 +96,13 @@ public class DefineClassHelper } }, JAVA_7 { + private final SecurityActions stack = SecurityActions.stack; private final MethodHandle defineClass = getDefineClassMethodHandle(); private final MethodHandle getDefineClassMethodHandle() { + if (null != SecuredPrivileged.JAVA_7 + && stack.getCallerClass() != this.getClass()) + throw new IllegalAccessError("Access denied for caller."); try { return SecurityActions.getMethodHandle(ClassLoader.class, "defineClass", new Class[] { @@ -109,6 +118,8 @@ public class DefineClassHelper protected Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain) throws ClassFormatError { + if (stack.getCallerClass() != DefineClassHelper.class) + throw new IllegalAccessError("Access denied for caller."); try { return (Class) defineClass.invokeWithArguments( loader, name, b, off, len, protectionDomain); @@ -121,7 +132,11 @@ public class DefineClassHelper }, JAVA_OTHER { private final Method defineClass = getDefineClassMethod(); + private final SecurityActions stack = SecurityActions.stack; private final Method getDefineClassMethod() { + if (null != SecuredPrivileged.JAVA_OTHER + && stack.getCallerClass() != this.getClass()) + throw new IllegalAccessError("Access denied for caller."); try { return SecurityActions.getDeclaredMethod(ClassLoader.class, "defineClass", new Class[] { diff --git a/src/main/javassist/util/proxy/SecurityActions.java b/src/main/javassist/util/proxy/SecurityActions.java index ce0b5fa6..4bc10a8a 100755 --- a/src/main/javassist/util/proxy/SecurityActions.java +++ b/src/main/javassist/util/proxy/SecurityActions.java @@ -25,8 +25,15 @@ import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +class SecurityActions extends SecurityManager +{ + public static final SecurityActions stack = new SecurityActions(); + public Class getCallerClass() + { + return getClassContext()[2]; + } + -class SecurityActions { static Method[] getDeclaredMethods(final Class clazz) { if (System.getSecurityManager() == null) -- cgit v1.2.3 From 45e201f2fbc0e75ee5c050f633e23ca5d418d52f Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 07:57:45 +0200 Subject: Give helper private constructor fwiw. Wont stop you from getting an instance but it makes it more trouble at least. Also reduce the visibility of the enum method. --- src/main/javassist/util/proxy/DefineClassHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/javassist/util/proxy/DefineClassHelper.java b/src/main/javassist/util/proxy/DefineClassHelper.java index b47f3b59..aebb5acb 100644 --- a/src/main/javassist/util/proxy/DefineClassHelper.java +++ b/src/main/javassist/util/proxy/DefineClassHelper.java @@ -170,7 +170,7 @@ public class DefineClassHelper }; - public abstract Class defineClass(String name, byte[] b, int off, int len, + protected abstract Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain) throws ClassFormatError; } @@ -231,4 +231,5 @@ public class DefineClassHelper } } + private DefineClassHelper() {} } -- cgit v1.2.3 From af399dbfa005ccde16247b88159cc269e243fb22 Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 07:59:12 +0200 Subject: Refactor definePackage as well. Yes it is deprecated but seems the right thing to do --- src/main/javassist/ClassPool.java | 69 +------- .../javassist/util/proxy/DefinePackageHelper.java | 179 +++++++++++++++++++++ 2 files changed, 184 insertions(+), 64 deletions(-) create mode 100644 src/main/javassist/util/proxy/DefinePackageHelper.java diff --git a/src/main/javassist/ClassPool.java b/src/main/javassist/ClassPool.java index 89a0067e..73a4d857 100644 --- a/src/main/javassist/ClassPool.java +++ b/src/main/javassist/ClassPool.java @@ -21,19 +21,16 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.lang.reflect.Method; import java.net.URL; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; -import java.util.Hashtable; -import java.util.Iterator; import java.util.ArrayList; import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Iterator; import javassist.bytecode.ClassFile; import javassist.bytecode.Descriptor; +import javassist.util.proxy.DefinePackageHelper; /** * A container of CtClass objects. @@ -69,28 +66,8 @@ import javassist.bytecode.Descriptor; * @see javassist.CtClass * @see javassist.ClassPath */ +@SuppressWarnings({"unchecked", "rawtypes"}) public class ClassPool { - private static java.lang.reflect.Method definePackage = null; - - static { - if (ClassFile.MAJOR_VERSION < ClassFile.JAVA_9) - try { - AccessController.doPrivileged(new PrivilegedExceptionAction(){ - public Object run() throws Exception{ - Class cl = Class.forName("java.lang.ClassLoader"); - definePackage = cl.getDeclaredMethod("definePackage", - new Class[] { String.class, String.class, String.class, - String.class, String.class, String.class, - String.class, java.net.URL.class }); - return null; - } - }); - } - catch (PrivilegedActionException pae) { - throw new RuntimeException("cannot initialize ClassPool", - pae.getException()); - } - } /** * Determines the search order. @@ -1175,43 +1152,7 @@ public class ClassPool { public void makePackage(ClassLoader loader, String name) throws CannotCompileException { - if (definePackage == null) - throw new CannotCompileException("give the JVM --add-opens"); - - Object[] args = new Object[] { - name, null, null, null, null, null, null, null }; - Throwable t; - try { - makePackage2(definePackage, loader, args); - return; - } - catch (java.lang.reflect.InvocationTargetException e) { - t = e.getTargetException(); - if (t == null) - t = e; - else if (t instanceof IllegalArgumentException) { - // if the package is already defined, an IllegalArgumentException - // is thrown. - return; - } - } - catch (Exception e) { - t = e; - } - - throw new CannotCompileException(t); + DefinePackageHelper.definePackage(name, loader); } - private static synchronized Object makePackage2(Method method, - ClassLoader loader, Object[] args) - throws Exception - { - method.setAccessible(true); - try { - return method.invoke(loader, args); - } - finally { - method.setAccessible(false); - } - } } diff --git a/src/main/javassist/util/proxy/DefinePackageHelper.java b/src/main/javassist/util/proxy/DefinePackageHelper.java new file mode 100644 index 00000000..1fe0bd61 --- /dev/null +++ b/src/main/javassist/util/proxy/DefinePackageHelper.java @@ -0,0 +1,179 @@ +/* + * Javassist, a Java-bytecode translator toolkit. + * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. Alternatively, the contents of this file may be used under + * the terms of the GNU Lesser General Public License Version 2.1 or later, + * or the Apache License Version 2.0. + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + */ + +package javassist.util.proxy; + +import java.lang.invoke.MethodHandle; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; + +import javassist.CannotCompileException; +import javassist.CtClass; +import javassist.bytecode.ClassFile; + +/** + * Helper class for invoking {@link ClassLoader#defineClass(String,byte[],int,int)}. + * + * @since 3.22 + */ +public class DefinePackageHelper +{ + + private static enum SecuredPrivileged + { + JAVA_9 { + // definePackage has been discontinued for JAVA 9 + @Override + protected Package definePackage(ClassLoader loader, String name, String specTitle, + String specVersion, String specVendor, String implTitle, String implVersion, + String implVendor, URL sealBase) throws IllegalArgumentException + { + throw new RuntimeException("define package has been disabled for jigsaw"); + } + }, + JAVA_7 { + private final SecurityActions stack = SecurityActions.stack; + private final MethodHandle definePackage = getDefinePackageMethodHandle(); + private MethodHandle getDefinePackageMethodHandle() + { + if (stack.getCallerClass() != this.getClass()) + throw new IllegalAccessError("Access denied for caller."); + try { + return SecurityActions.getMethodHandle(ClassLoader.class, + "definePackage", new Class[] { + String.class, String.class, String.class, String.class, + String.class, String.class, String.class, URL.class + }); + } catch (NoSuchMethodException e) { + throw new RuntimeException("cannot initialize", e); + } + } + + @Override + protected Package definePackage(ClassLoader loader, String name, String specTitle, + String specVersion, String specVendor, String implTitle, String implVersion, + String implVendor, URL sealBase) throws IllegalArgumentException { + if (stack.getCallerClass() != DefinePackageHelper.class) + throw new IllegalAccessError("Access denied for caller."); + try { + return (Package) definePackage.invokeWithArguments(loader, name, specTitle, + specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); + } catch (Throwable e) { + if (e instanceof IllegalArgumentException) throw (IllegalArgumentException) e; + if (e instanceof RuntimeException) throw (RuntimeException) e; + } + return null; + } + }, + JAVA_OTHER { + private final SecurityActions stack = SecurityActions.stack; + private final Method definePackage = getDefinePackageMethod(); + private Method getDefinePackageMethod() + { + if (stack.getCallerClass() != this.getClass()) + throw new IllegalAccessError("Access denied for caller."); + try { + return SecurityActions.getDeclaredMethod(ClassLoader.class, + "definePackage", new Class[] { + String.class, String.class, String.class, String.class, + String.class, String.class, String.class, URL.class + }); + } catch (NoSuchMethodException e) { + throw new RuntimeException("cannot initialize", e); + } + } + + @Override + protected Package definePackage(ClassLoader loader, String name, String specTitle, + String specVersion, String specVendor, String implTitle, String implVersion, + String implVendor, URL sealBase) throws IllegalArgumentException + { + if (stack.getCallerClass() != DefinePackageHelper.class) + throw new IllegalAccessError("Access denied for caller."); + try { + definePackage.setAccessible(true); + return (Package) definePackage.invoke(loader, new Object[] { + name, specTitle, specVersion, specVendor, implTitle, + implVersion, implVendor, sealBase + }); + } catch (Throwable e) { + if (e instanceof InvocationTargetException) { + Throwable t = ((InvocationTargetException) e).getTargetException(); + if (t instanceof IllegalArgumentException) + throw (IllegalArgumentException) t; + } + if (e instanceof RuntimeException) throw (RuntimeException) e; + } + finally { + definePackage.setAccessible(false); + } + return null; + } + }; + + protected abstract Package definePackage(ClassLoader loader, String name, String specTitle, + String specVersion, String specVendor, String implTitle, String implVersion, + String implVendor, URL sealBase) throws IllegalArgumentException; + } + + private static final SecuredPrivileged privileged = ClassFile.MAJOR_VERSION >= ClassFile.JAVA_9 + ? SecuredPrivileged.JAVA_9 + : ClassFile.MAJOR_VERSION >= ClassFile.JAVA_7 + ? SecuredPrivileged.JAVA_7 + : SecuredPrivileged.JAVA_OTHER; + + + /** + * Defines a new package. If the package is already defined, this method + * performs nothing. + * + *

You do not necessarily need to + * call this method. If this method is called, then + * getPackage() on the Class object returned + * by toClass() will return a non-null object.

+ * + *

The jigsaw module introduced by Java 9 has broken this method. + * In Java 9 or later, the VM argument + * --add-opens java.base/java.lang=ALL-UNNAMED + * has to be given to the JVM so that this method can run. + *

+ * + * @param loader the class loader passed to toClass() or + * the default one obtained by getClassLoader(). + * @param name the package name. + * @see #getClassLoader() + * @see #toClass(CtClass) + * @see CtClass#toClass() */ + public static void definePackage(String className, ClassLoader loader) + throws CannotCompileException + { + try { + privileged.definePackage(loader, className, + null, null, null, null, null, null, null); + } + catch (IllegalArgumentException e) { + // if the package is already defined, an IllegalArgumentException + // is thrown. + return; + } + catch (Exception e) { + throw new CannotCompileException(e); + } + } + + private DefinePackageHelper() {} +} -- cgit v1.2.3 From 50dd54dc0552960c80aacded54a9ff7fe0f747f6 Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 08:10:22 +0200 Subject: Even now Oracle still not happy. Runtime still throws warnings illegal actions even after all that, there's just no logic to it. We can use Unsafe but don't touch setAccessible. O well might as well enjoy Unsafe while we still can. Wrapped the Unsafe and added method cache with varargs method for invoke calls. Can still do a lot with it but it does what it needs to for now. --- .../javassist/util/proxy/DefineClassHelper.java | 15 +++--- src/main/javassist/util/proxy/SecurityActions.java | 61 ++++++++++++++++++++-- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/main/javassist/util/proxy/DefineClassHelper.java b/src/main/javassist/util/proxy/DefineClassHelper.java index aebb5acb..3ed261e5 100644 --- a/src/main/javassist/util/proxy/DefineClassHelper.java +++ b/src/main/javassist/util/proxy/DefineClassHelper.java @@ -38,10 +38,10 @@ public class DefineClassHelper JAVA_9 { final class ReferencedUnsafe { - private final Object sunMiscUnsafeTheUnsafe; + private final SecurityActions.TheUnsafe sunMiscUnsafeTheUnsafe; private final MethodHandle defineClass; - ReferencedUnsafe(Object usf, MethodHandle meth) + ReferencedUnsafe(SecurityActions.TheUnsafe usf, MethodHandle meth) { this.sunMiscUnsafeTheUnsafe = usf; this.defineClass = meth; @@ -54,7 +54,7 @@ public class DefineClassHelper throw new IllegalAccessError("Access denied for caller."); try { return (Class) defineClass.invokeWithArguments( - sunMiscUnsafeTheUnsafe, + sunMiscUnsafeTheUnsafe.theUnsafe, name, b, off, len, loader, protectionDomain); } catch (Throwable e) { if (e instanceof RuntimeException) throw (RuntimeException) e; @@ -72,12 +72,9 @@ public class DefineClassHelper && stack.getCallerClass() != this.getClass()) throw new IllegalAccessError("Access denied for caller."); try { - Object usf = SecurityActions.getSunMiscUnsafeAnonymously(); - MethodHandle meth = SecurityActions.getMethodHandle(ClassLoader.class, - "defineClass", new Class[] { - String.class, byte[].class, int.class, int.class, - ProtectionDomain.class - }); + SecurityActions.TheUnsafe usf = SecurityActions.getSunMiscUnsafeAnonymously(); + MethodHandle meth = MethodHandles.lookup() + .unreflect(usf.methods.get("defineClass").get(0)); return new ReferencedUnsafe(usf, meth); } catch (Throwable e) { throw new RuntimeException("cannot initialize", e); diff --git a/src/main/javassist/util/proxy/SecurityActions.java b/src/main/javassist/util/proxy/SecurityActions.java index 4bc10a8a..574073d0 100755 --- a/src/main/javassist/util/proxy/SecurityActions.java +++ b/src/main/javassist/util/proxy/SecurityActions.java @@ -25,6 +25,14 @@ import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javassist.bytecode.ClassFile; + class SecurityActions extends SecurityManager { public static final SecurityActions stack = new SecurityActions(); @@ -170,18 +178,19 @@ class SecurityActions extends SecurityManager } } - static Object getSunMiscUnsafeAnonymously() throws ClassNotFoundException + static TheUnsafe getSunMiscUnsafeAnonymously() throws ClassNotFoundException { try { return AccessController.doPrivileged( - new PrivilegedExceptionAction() { public Object run() throws + new PrivilegedExceptionAction() { public TheUnsafe run() throws ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException { Class unsafe = Class.forName("sun.misc.Unsafe"); Field theUnsafe = unsafe.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true); - Object usf = theUnsafe.get(null); + TheUnsafe usf = stack.new TheUnsafe(unsafe, theUnsafe.get(null)); theUnsafe.setAccessible(false); + disableWarning(usf); return usf; } }); @@ -198,4 +207,50 @@ class SecurityActions extends SecurityManager throw new RuntimeException(e.getCause()); } } + class TheUnsafe + { + final Class unsafe; + final Object theUnsafe; + final Map> methods = + new HashMap>(); + + TheUnsafe(Class c, Object o) + { + this.unsafe = c; + this.theUnsafe = o; + for (Method m: unsafe.getDeclaredMethods()) { + if (!methods.containsKey(m.getName())) { + methods.put(m.getName(), Collections.singletonList(m)); + continue; + } + if (methods.get(m.getName()).size() == 1) + methods.put(m.getName(), + new ArrayList(methods.get(m.getName()))); + methods.get(m.getName()).add(m); + } + } + + private Method getM(String name, Object[] o) + { + return methods.get(name).get(0); + } + + public Object call(String name, Object... args) + { + try { + return getM(name, args).invoke(theUnsafe, args); + } catch (Throwable t) {t.printStackTrace();} + return null; + } + } + static void disableWarning(TheUnsafe tu) { + try { + if (ClassFile.MAJOR_VERSION < ClassFile.JAVA_9) + return; + Class cls = Class.forName("jdk.internal.module.IllegalAccessLogger"); + Field logger = cls.getDeclaredField("logger"); + tu.call("putObjectVolatile", cls, tu.call("staticFieldOffset", logger), null); + } catch (Exception e) { /*swallow*/ } + } } + -- cgit v1.2.3 From df58ae940da38be8e08f35d72f7acb488545ef3b Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 08:12:07 +0200 Subject: Some code comments to fill in gaps. --- src/main/javassist/util/proxy/SecurityActions.java | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/main/javassist/util/proxy/SecurityActions.java b/src/main/javassist/util/proxy/SecurityActions.java index 574073d0..acb89c8d 100755 --- a/src/main/javassist/util/proxy/SecurityActions.java +++ b/src/main/javassist/util/proxy/SecurityActions.java @@ -36,6 +36,18 @@ import javassist.bytecode.ClassFile; class SecurityActions extends SecurityManager { public static final SecurityActions stack = new SecurityActions(); + /** + * Since Java 9 abruptly removed Reflection.getCallerClass() + * in favour of StackWalker we are left having to find a + * solution for the older versions without upsetting the new compiler. + * + * The member scoped function getClassContext() + * available as a SecurityManager sibling remains + * functional across all versions, for now. + * + * @return represents the declaring class of the method that invoked + * the method that called this or idx 2 on the stack trace. + * @since 3.23 */ public Class getCallerClass() { return getClassContext()[2]; @@ -207,6 +219,18 @@ class SecurityActions extends SecurityManager throw new RuntimeException(e.getCause()); } } + /** + * _The_ Notorious sun.misc.Unsafe in all its glory, but anonymous + * so as not to attract unwanted attention. Kept in two separate + * parts it manages to avoid detection from linker/compiler/general + * complainers and those. This functionality will vanish from the + * JDK soon but in the meantime it shouldn't be an obstacle. + * + * All exposed methods are cached in a dictionary with overloaded + * methods collected under their corresponding keys. Currently the + * implementation assumes there is only one, if you need find a + * need there will have to be a compare. + * @since 3.23 */ class TheUnsafe { final Class unsafe; @@ -243,6 +267,12 @@ class SecurityActions extends SecurityManager return null; } } + /** + * Java 9 now complains about every privileged action regardless. + * Displaying warnings of "illegal usage" and then instructing users + * to go hassle the maintainers in order to have it fixed. + * Making it hush for now, see all fixed. + * @param tu theUnsafe that'll fix it */ static void disableWarning(TheUnsafe tu) { try { if (ClassFile.MAJOR_VERSION < ClassFile.JAVA_9) -- cgit v1.2.3 From 7d3d65ec97bfeb6071af99ffda3d95797ae91b9c Mon Sep 17 00:00:00 2001 From: nickl- Date: Fri, 27 Oct 2017 08:33:55 +0200 Subject: Some functional unit tests proofs. Proves that you cannot access restricted functionality then goes ahead and mock test all 3 exposures JAVA_9, JAVA_7+ and OTHERS by changing the private static final class referenced default chosen on initialise. --- .../javassist/proxy/TestSecuredPrivileged.java | 281 +++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 src/test/test/javassist/proxy/TestSecuredPrivileged.java diff --git a/src/test/test/javassist/proxy/TestSecuredPrivileged.java b/src/test/test/javassist/proxy/TestSecuredPrivileged.java new file mode 100644 index 00000000..c51555f5 --- /dev/null +++ b/src/test/test/javassist/proxy/TestSecuredPrivileged.java @@ -0,0 +1,281 @@ +package test.javassist.proxy; +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.both; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.security.ProtectionDomain; +import java.util.Arrays; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import javassist.ClassPool; +import javassist.CtClass; +import javassist.util.proxy.DefineClassHelper; + +public class TestSecuredPrivileged { + + public TestSecuredPrivileged() { + } + + @Rule + public ExpectedException thrown = ExpectedException.none(); + /** + * Test proves that you cannot even access members with + * private static and final modifiers. */ + @Test + public void testDefinedHelperPrivilegedFieldVisibility() { + try { + Field privi = DefineClassHelper.class.getDeclaredField("privileged"); + assertTrue(Modifier.isStatic(privi.getModifiers())); + thrown.expectCause(instanceOf(IllegalAccessException.class)); + thrown.expectMessage(both(stringContainsInOrder(Arrays.asList("cannot access a member"))) + .and(stringContainsInOrder(Arrays.asList("with modifiers \"private static final".split("", 1))))); + + privi.get(null); + } catch(Throwable t) { + throw new RuntimeException(t); + } + } + /** + * Test proves that the default enum constant is a class and specifically + * auto selected for Java 9. */ + @Test + public void testDefinedHelperPrivilegedField() { + try { + Field privi = DefineClassHelper.class.getDeclaredField("privileged"); + assertTrue(Modifier.isStatic(privi.getModifiers())); + Constructor con = DefineClassHelper.class.getDeclaredConstructor(); + con.setAccessible(true); + DefineClassHelper inst = con.newInstance(); + assertThat(inst, instanceOf(DefineClassHelper.class)); + privi.setAccessible(true); + Object p = privi.get(inst); + assertThat(""+p, equalTo("JAVA_9")); + assertThat(p.getClass().getName(), endsWith("SecuredPrivileged$1")); + } catch(Throwable t) { + throw new RuntimeException(t); + } + } + /** + * Test proves that caller class security is enforced and works + * as expected. */ + @Test + public void testDefinedHelperPrivilegedFieldMethodAccessDenied() { + try { + Constructor con = DefineClassHelper.class.getDeclaredConstructor(); + con.setAccessible(true); + DefineClassHelper inst = con.newInstance(); + Field privi = DefineClassHelper.class.getDeclaredField("privileged"); + privi.setAccessible(true); + Object priviInst = privi.get(inst); + Method defineClass = priviInst.getClass().getDeclaredMethod( + "defineClass", new Class[] { + String.class, byte[].class, int.class, int.class, + ClassLoader.class, ProtectionDomain.class + }); + + assertThat(defineClass, notNullValue()); + defineClass.setAccessible(true); + assertThat(defineClass.getName(), equalTo("defineClass")); + assertTrue(defineClass.canAccess(priviInst)); + ClassPool cp = ClassPool.getDefault(); + CtClass c = cp.makeClass("a.b.C"); + byte[] bc = c.toBytecode(); + + thrown.expectCause(instanceOf(IllegalAccessError.class)); + thrown.expectMessage(equalTo("java.lang.IllegalAccessError: Access denied for caller.")); + + @SuppressWarnings("unused") + Object res = defineClass.invoke(priviInst, new Object[] { + c.getName(), bc, 0, bc.length, new ClassLoader() {}, + ClassLoader.class.getProtectionDomain() + }); + } catch(InvocationTargetException t) { + throw new RuntimeException(t.getTargetException()); + } catch(Throwable t) { throw new RuntimeException(t); } + } + /** + * Test proves that we do have 3 enum constants in the private static + * inner class. */ + @Test + public void testDefinedHelperEnumClass() { + try { + Constructor con = DefineClassHelper.class.getDeclaredConstructor(); + con.setAccessible(true); + assertThat(DefineClassHelper.class.getDeclaredClasses(), arrayWithSize(1)); + Class secPriv = DefineClassHelper.class.getDeclaredClasses()[0]; + assertTrue(secPriv.isEnum()); + assertThat(secPriv.getEnumConstants(), arrayWithSize(3)); + assertThat(""+secPriv.getEnumConstants()[0], equalTo("JAVA_9")); + assertThat(""+secPriv.getEnumConstants()[1], equalTo("JAVA_7")); + assertThat(""+secPriv.getEnumConstants()[2], equalTo("JAVA_OTHER")); + + } catch (Throwable t) {t.printStackTrace();} + + } + /** + * Test proves that you cannot modify private static final reference even + * with setAccessible(true). */ + @Test + public void testDefinedHelperCannotSetPrivileged() { + try { + Constructor con = DefineClassHelper.class.getDeclaredConstructor(); + con.setAccessible(true); + DefineClassHelper inst = con.newInstance(); + Class secPriv = DefineClassHelper.class.getDeclaredClasses()[0]; + Object J7 = secPriv.getEnumConstants()[1]; + Field privi = DefineClassHelper.class.getDeclaredField("privileged"); + privi.setAccessible(true); + thrown.expectCause(instanceOf(IllegalAccessException.class)); + thrown.expectMessage(startsWith("java.lang.IllegalAccessException: Can not set static final")); + privi.set(inst, J7); + + } catch (Throwable t) {throw new RuntimeException(t);} + + } + /** + * Test proves that you can achieve the impossible and modify private + * static final class reference without an instance. Now we can Mock + * test JDK 6 to 8 functionality */ + @Test + public void testDefinedHelperSetPrivilegedToJava7() { + try { + Constructor con = DefineClassHelper.class.getDeclaredConstructor(); + con.setAccessible(true); + DefineClassHelper inst = con.newInstance(); + Class secPriv = DefineClassHelper.class.getDeclaredClasses()[0]; + Object J9 = secPriv.getEnumConstants()[0]; + Object J7 = secPriv.getEnumConstants()[1]; + Field privi = DefineClassHelper.class.getDeclaredField("privileged"); + privi.setAccessible(true); + Object privInst = privi.get(inst); + Field unsf = privInst.getClass().getDeclaredField("sunMiscUnsafe"); + unsf.setAccessible(true); + Object refu = unsf.get(privInst); + Field tuf = refu.getClass().getDeclaredField("sunMiscUnsafeTheUnsafe"); + tuf.setAccessible(true); + Object tu = tuf.get(refu); + Method tu_call = tu.getClass().getMethod("call", new Class[] {String.class, Object[].class}); + tu_call.setAccessible(true); + long offset = (Long) tu_call.invoke(tu, new Object[] {"staticFieldOffset", new Object[] {privi}}); + tu_call.invoke(tu, new Object[] {"putObjectVolatile", new Object[] {DefineClassHelper.class, offset, J7}}); + + Object p = privi.get(inst); + assertThat(""+p, equalTo("JAVA_7")); + assertThat(p.getClass().getName(), endsWith("SecuredPrivileged$2")); + + tu_call.invoke(tu, new Object[] {"putObjectVolatile", new Object[] {DefineClassHelper.class, offset, J9}}); + + } catch (Throwable t) {t.printStackTrace();} + + } + /** + * Test proves that Java 7+ MethodHandle defineClass (or DefineClassHelper.toClass) + * works as expected. */ + @Test + public void testDefinedHelperJava7ToClass() { + try { + Constructor con = DefineClassHelper.class.getDeclaredConstructor(); + con.setAccessible(true); + DefineClassHelper inst = con.newInstance(); + Class secPriv = DefineClassHelper.class.getDeclaredClasses()[0]; + Object J9 = secPriv.getEnumConstants()[0]; + Object J7 = secPriv.getEnumConstants()[1]; + Field privi = DefineClassHelper.class.getDeclaredField("privileged"); + privi.setAccessible(true); + Object privInst = privi.get(inst); + Field unsf = privInst.getClass().getDeclaredField("sunMiscUnsafe"); + unsf.setAccessible(true); + Object refu = unsf.get(privInst); + Field tuf = refu.getClass().getDeclaredField("sunMiscUnsafeTheUnsafe"); + tuf.setAccessible(true); + Object tu = tuf.get(refu); + Method tu_call = tu.getClass().getMethod("call", new Class[] {String.class, Object[].class}); + tu_call.setAccessible(true); + long offset = (Long) tu_call.invoke(tu, new Object[] {"staticFieldOffset", new Object[] {privi}}); + tu_call.invoke(tu, new Object[] {"putObjectVolatile", new Object[] {DefineClassHelper.class, offset, J7}}); + + ClassPool cp = ClassPool.getDefault(); + CtClass c = cp.makeClass("a.b.J7"); + byte[] bc = c.toBytecode(); + Class bcCls = DefineClassHelper.toClass("a.b.J7", new ClassLoader() {}, null, bc); + assertThat(bcCls.getName(), equalTo("a.b.J7")); + assertThat(bcCls.getDeclaredConstructor().newInstance(), + not(equalTo(bcCls.getDeclaredConstructor().newInstance()))); + + tu_call.invoke(tu, new Object[] {"putObjectVolatile", new Object[] {DefineClassHelper.class, offset, J9}}); + + } catch (Throwable t) {t.printStackTrace();} + + } + /** + * Test proves that Java 6 reflection method defineClass (or DefineClassHelper.toClass) + * works as expected. */ + @Test + public void testDefinedHelperJavaOtherToClass() { + try { + Constructor con = DefineClassHelper.class.getDeclaredConstructor(); + con.setAccessible(true); + DefineClassHelper inst = con.newInstance(); + Class secPriv = DefineClassHelper.class.getDeclaredClasses()[0]; + Object J9 = secPriv.getEnumConstants()[0]; + Object JO = secPriv.getEnumConstants()[2]; + Field privi = DefineClassHelper.class.getDeclaredField("privileged"); + privi.setAccessible(true); + Object privInst = privi.get(inst); + Field unsf = privInst.getClass().getDeclaredField("sunMiscUnsafe"); + unsf.setAccessible(true); + Object refu = unsf.get(privInst); + Field tuf = refu.getClass().getDeclaredField("sunMiscUnsafeTheUnsafe"); + tuf.setAccessible(true); + Object tu = tuf.get(refu); + Method tu_call = tu.getClass().getMethod("call", new Class[] {String.class, Object[].class}); + tu_call.setAccessible(true); + long offset = (Long) tu_call.invoke(tu, new Object[] {"staticFieldOffset", new Object[] {privi}}); + tu_call.invoke(tu, new Object[] {"putObjectVolatile", new Object[] {DefineClassHelper.class, offset, JO}}); + + ClassPool cp = ClassPool.getDefault(); + CtClass c = cp.makeClass("a.b.JO"); + byte[] bc = c.toBytecode(); + Class bcCls = DefineClassHelper.toClass("a.b.JO", new ClassLoader() {}, null, bc); + assertThat(bcCls.getName(), equalTo("a.b.JO")); + assertThat(bcCls.getDeclaredConstructor().newInstance(), + not(equalTo(bcCls.getDeclaredConstructor().newInstance()))); + + tu_call.invoke(tu, new Object[] {"putObjectVolatile", new Object[] {DefineClassHelper.class, offset, J9}}); + + } catch (Throwable t) {t.printStackTrace();} + + } + /** + * Test proves that default Java 9 defineClass (or DefineClassHelper.toClass) + * works as expected. */ + @Test + public void testDefinedHelperDefaultToClass() { + try { + ClassPool cp = ClassPool.getDefault(); + CtClass c = cp.makeClass("a.b.D"); + byte[] bc = c.toBytecode(); + Class bcCls = DefineClassHelper.toClass("a.b.D", new ClassLoader() {}, null, bc); + assertThat(bcCls.getName(), equalTo("a.b.D")); + assertThat(bcCls.getDeclaredConstructor().newInstance(), + not(equalTo(bcCls.getDeclaredConstructor().newInstance()))); + } catch (Throwable t) {t.printStackTrace();} + + } +} -- cgit v1.2.3 From ceebba361131a2e2e328147d3c5ad631bc96f282 Mon Sep 17 00:00:00 2001 From: nickl- Date: Mon, 30 Oct 2017 00:53:08 +0200 Subject: Define parameters for returned iterator. --- src/main/javassist/ClassPool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/javassist/ClassPool.java b/src/main/javassist/ClassPool.java index 73a4d857..60055992 100644 --- a/src/main/javassist/ClassPool.java +++ b/src/main/javassist/ClassPool.java @@ -298,7 +298,7 @@ public class ClassPool { * @see #importPackage(String) * @since 3.1 */ - public Iterator getImportedPackages() { + public Iterator getImportedPackages() { return importedPackages.iterator(); } -- cgit v1.2.3