帖子

Memorial Edition

查看: 61|回复: 0

[插件开发教程] Write Up : MCG/Future:面向高版本JDK的探讨

[复制链接]

男同大王

人气
15 点
金粒
463 粒
宝石
0 颗
爱心
1 颗
钻石
79 颗
贡献
0 点
发表于 2 小时前 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 huzpsb 于 2025-2-5 15:51 编辑

Write Up : MCG/Future:面向高版本JDK的探讨
MCG/Future的可能实现
【警告】
MCG是一个EOL项目,而本系列文章的公开无疑会再次降低MCG的安全性。无论如何,请不要再使用MCG
本系列文章旨在分享MCG的思路而不是源码;请不要尝试通过简单的复制粘贴来完成对MCG的重建。
本文涉及到UB(Undefined behavior),和大量JVM无文档API,请持审慎态度看待本篇文章!
恭喜你来到了JVM的深暗之域



▌Part 1 JEP260
1.1 内容
Encapsulate Most Internal APIs的大义是利用JEP200与JEP261来控制对Field的访问。考虑以下代码:
  1. Method m = System.class.getDeclaredMethod("setJavaLangAccess");
  2. m.setAccessible(true);
复制代码
这段代码在Java8中是完全正确的。但是在Java21中,它会产生以下报错:
  1. Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make private static void java.lang.System.setJavaLangAccess() accessible: module java.base does not "opens java.lang" to unnamed module @4e50df2e
  2.         at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:391)
  3.         at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:367)
  4.         at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:315)
  5.         at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:203)
  6.         at java.base/java.lang.reflect.Method.setAccessible(Method.java:197)
复制代码
这是因为,java.lang并不对当前模块开放。当然,受到推荐的做法有:
- 不使用JVM的内部API
- 在启动参数中添加--add-opens java.base/java.lang=ALL-UNNAMED
但是,前者无疑会使得本文失去探讨的意义;后者会增加用户的工作量。事实上,它会劝退很多用户!

1.2 解决
那么,有没有解决方案呢?首先让我们检查setAccessible
  1. <blockquote>@Override
复制代码
一个自然的思路就是,把setAccessible0拿到手。这可行吗?
让我们试一试。
  1. Method m = AccessibleObject.class.getDeclaredMethod("setAccessible0", boolean.class);
  2. m.setAccessible(true);
复制代码
结果是
  1. Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make boolean java.lang.reflect.AccessibleObject.setAccessible0(boolean) accessible: module java.base does not "opens java.lang.reflect" to unnamed module @4e50df2e
复制代码
这说明这个思路不可行——setAccessible0需要被setAccessible,这是循环依赖。就要在这里结束了吗?
让我们仔细阅读JEP260:
Critical internal APIs not encapsulated in JDK 9
The critical internal APIs that are not encapsulated in JDK 9, because supported replacements did not exist in JDK 8, are listed here.
省略号
sun.reflect.ReflectionFactory
省略号

这个sun.reflect.ReflectionFactory是什么东西?让我们看看?
ReflectionFactory supports custom serialization.
Its methods support the creation of uninitialized objects, invoking serialization
private methods for readObject, writeObject, readResolve, and writeReplace.

什么又臭又长的,我只看得到private methods
快端上来吧!
  1. Constructor<?> ctor = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(MethodHandles.Lookup.class, MethodHandles.Lookup.class.getDeclaredConstructor(Class.class));
  2. Class<AccessibleObject> aoc = AccessibleObject.class;
  3. MethodHandles.Lookup lookup = (MethodHandles.Lookup) ctor.newInstance(aoc);
  4. MethodHandle export = lookup.findVirtual(aoc, "setAccessible0", MethodType.methodType(Boolean.TYPE, Boolean.TYPE));
  5. export.invoke(ao, true);
复制代码
其中ao是需要操作的AccessibleObject。至此,我们已经可以任意开放AccessibleObject的权限了。



▌Part 2 JEP486
2.1 内容
Permanently Disable the Security Manager的大义是肘击SecurityManager使其不可用。考虑以下代码:
  1. System.setSecurityManager(new SecurityManager());
复制代码
这段代码在Java8中是完全正确的。但是在Java21中,它会产生以下报错:
  1. Exception in thread "main" java.lang.UnsupportedOperationException: The Security Manager is deprecated and will be removed in a future release
复制代码
让我们跟踪一下原因。JEP486引入了以下代码:
  1. // return true if a security manager is allowed
复制代码
这是很坏的。

2.2 解决?

我们在《Write Up : MCG/DynaGuard:JVM层HIPS的原理与实现》已经提到过关于getDeclaredFields0的获取与使用;在这里不再赘述。
现在我们能够获取并正确访问到allowSecurityManager字段了。我们只需要
  1. allowSecurityManagerField.set(null, 2);
复制代码
对吧?
对吗?!
报错怎么还在?没关系,让我们打一个断点,看看为什么值未设置成功。
问题开始浮出水面——当你在allowSecurityManager方法处下断点的一瞬间,这个报错就不会再出现了。

2.3 解决
合理怀疑,是JVMTI的存在使得对应的方法脱离了JIT状态。因此我们只需要使用-client或者redefine一下System使其脱离JIT状态即可
但是为什么JIT会带来脏数据呢?allowSecurityManager甚至不是final字段。让我们仔细检查一下allowSecurityManager字段的定义:
  1. private static @Stable int allowSecurityManager;
复制代码
Stable是什么意思?让我们看看javadoc:
The HotSpot VM relies on this annotation to promote a non-null (resp.,
non-zero) component value to a constant, thereby enabling superior
optimizations of code depending on such a value (such as constant folding).
More specifically, the HotSpot VM will process non-null stable fields (final
or otherwise) in a similar manner to static final fields with respect to
promoting the field's value to a constant.  Thus, placing aside the
differences for null/non-null values and arrays, a final stable field is
treated as if it is really final from both the Java language and the HotSpot
VM.
It is (currently) undefined what happens if a field annotated as stable
is given a third value (by explicitly updating a stable field, a component of
a stable array, or a final stable field via reflection or other means).
Since the HotSpot VM promotes a non-null component value to constant, it may
be that the Java memory model would appear to be broken, if such a constant
(the second value of the field) is used as the value of the field even after
the field value has changed (to a third value).

好好好,简单来说,Stable的意思是比final更强的inline。修改Stable字段的值是UB。
那么我们需要一种方法来刷新脏数据。JIT的问题应该让JIT解决——比如说触发JIT/C2怎么样?只需要调用10w次就可以了。
  1. int jit = 100005;
  2. SecurityManager sm = new SecurityManager();
  3. while (true) {
  4.     jit -= 1;
  5.     if (jit <= 0) {
  6.         throw new RuntimeException("JIT failed?!");
  7.     }
  8.     try {
  9.         System.setSecurityManager(sm);
  10.         break;
  11.     } catch (Throwable t) {
  12.     }
  13. }
复制代码
搞定!
此外我还想不加解释地附上关闭警告的代码。
  1. Class<?> clz = Class.forName("java.lang.System$CallersHolder");
  2. Field callersField = clz.getDeclaredField("callers");
  3. makeAccessible(callersField); // 第一章提到的方法
  4. Map map = (Map) callersField.get(null);
  5. map.put(你的clz, true);
复制代码




不过目前看来我确实是没动力将这部分内容实装进MCG了
就这样吧?
以上,hs


——END——



评分

参与人数 1人气 +3 收起 理由
洞穴夜莺 + 3 不过,为什么要设置SecurityManager? ...

查看全部评分

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

不要轻易的口出狂言,那样只会透露出你的软弱。

Archiver|小黑屋| MCBBS纪念版 ( 新ICP备2024014954号|兵公网安备66010002000149号 )|隐私政策| 手机版

GMT+8, 2025-2-5 17:58 , Processed in 0.103820 second(s), 18 queries , Redis On.

"Minecraft"以及"我的世界"为美国微软公司的商标 本站与微软公司没有从属关系

© 2010-2025 MCBBS纪念版 版权所有 本站内原创内容版权属于其原创作者,除作者或版规特别声明外未经许可不得转载

返回顶部