一尘不染

如何将setAccessible限制为仅“合法”使用?

java

我对的能力了解得越多,我java.lang.reflect.AccessibleObject.setAccessible就越惊讶于它的作用。这是根据我对问题的回答(使用反射更改静态最终File.separatorChar用于单元测试)改编而成的。

import java.lang.reflect.*;

public class EverythingIsTrue {
   static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }
   public static void main(String args[]) throws Exception {      
      setFinalStatic(Boolean.class.getField("FALSE"), true);

      System.out.format("Everything is %s", false); // "Everything is true"
   }
}

你可以做真正令人发指的事情:

public class UltimateAnswerToEverything {
   static Integer[] ultimateAnswer() {
      Integer[] ret = new Integer[256];
      java.util.Arrays.fill(ret, 42);
      return ret;
   }   
   public static void main(String args[]) throws Exception {
      EverythingIsTrue.setFinalStatic(
         Class.forName("java.lang.Integer$IntegerCache")
            .getDeclaredField("cache"),
         ultimateAnswer()
      );
      System.out.format("6 * 9 = %d", 6 * 9); // "6 * 9 = 42"
   }
}

大概是API设计者意识到了可滥用性setAccessible,但是必须承认它具有合法的用途来提供它。所以我的问题是:

  • 真正合法的用途是setAccessible什么?
  • Java是否可以被设计为一开始就没有这种需求?
  • 这种设计的负面后果(如果有)是什么?
  • setAccessible只能限制合法使用吗?
  • 只有通过SecurityManager吗?
  • 它是如何工作的?白名单/黑名单,粒度等?
    • 必须在你的应用程序中对其进行配置是否常见?
    • setAccessible无论SecurityManager配置如何,我都可以将自己的类写成-proof 吗?
  • 还是由谁来管理配置?

我猜一个更重要的问题是:我是否需要为此担心???

我的课程都没有任何类似的可执行隐私。现在无法执行单例模式(不考虑其优缺点)。正如我上面的摘录所示,甚至几乎无法保证有关Java基础如何工作的一些基本假设。

这些问题不是真的吗???

好的,我刚刚确认:感谢setAccessible,Java字符串不是不可变的。

import java.lang.reflect.*;

public class MutableStrings {
   static void mutate(String s) throws Exception {
      Field value = String.class.getDeclaredField("value");
      value.setAccessible(true);
      value.set(s, s.toUpperCase().toCharArray());
   }   
   public static void main(String args[]) throws Exception {
      final String s = "Hello world!";
      System.out.println(s); // "Hello world!"
      mutate(s);
      System.out.println(s); // "HELLO WORLD!"
   }
}

我是唯一认为这是一个巨大问题的人吗?


阅读 684

收藏
2020-03-23

共1个答案

一尘不染

我需要为此担心吗???

这完全取决于你正在编写哪种类型的程序以及哪种体系结构。

如果你要向世界各地的人们分发名为foo.jar的软件组件,则无论如何你完全会屈从于他们。他们可以修改.jar中的类定义(通过逆向工程或直接字节码操作)。他们可以在自己的JVM中运行你的代码,等等。在这种情况下,担心将无济于事。

如果你正在编写仅通过HTTP与人和系统交互的Web应用程序,并且可以控制应用程序服务器,那么也不必担心。确保你公司的同等编码人员可以创建破坏你单例模式的代码,但前提是他们确实愿意。

如果你将来的工作是在Sun Microsystems / Oracle上编写代码,而你的任务是为Java核心或其他受信任的组件编写代码,那么你应该意识到这一点。但是,担心只会使你掉头发。无论如何,它们可能会让你阅读《安全编码指南》以及内部文档。

如果要编写Java applet,则应注意安全框架。你会发现尝试调用setAccessible的未签名小程序只会导致SecurityException。

setAccessible并不是围绕常规完整性检查的唯一方法。有一个名为sun.misc.Unsafe的非API核心Java类,它可以做几乎所有它想做的事情,包括直接访问内存。本机代码(JNI)也可以绕过这种控制。

在沙盒环境(例如Java Applets,JavaFX)中,每个类都有一组权限,对不安全,setAccessible的访问以及定义的本机实现均由SecurityManager控制。

“ Java访问修饰符并非旨在成为一种安全机制。”

这很大程度上取决于Java代码的运行位置。核心Java类确实使用访问修饰符作为强制执行沙箱的安全机制。

setAccessible的真正合法用途是什么?

Java核心类使用它作为一种简单的方法来访问出于安全原因而必须保持私有的内容。作为示例,Java序列化框架在反序列化对象时使用它来调用私有对象构造函数。有人提到System.setErr,这将是一个很好的示例,但是奇怪的是System类方法setOut / setErr / setIn都使用本机代码来设置final字段的值。

另一个明显的合法用途是需要窥探对象内部的框架(持久性,Web框架,注入)。

在我看来,调试器不属于此类,因为它们通常不在同一JVM进程中运行,而是使用其他方法(JPDA)与JVM进行接口。

Java是否可以被设计为一开始就没有这种需求?

要回答这个问题,这是一个很深的问题。我想是的,但是你需要添加一些其他机制,而这些机制可能并不是所有的首选。

你可以将setAccessible限制为仅合法使用吗?

你可以应用的最简单的OOTB限制是拥有SecurityManager并只允许setAccessible来访问来自某些来源的代码。这是Java已经做的-允许来自JAVA_HOME的标准Java类执行setAccessible,而不允许来自foo.com的未签名applet类进行setAccessible。如前所述,这种许可是二进制的,就某种意义上来说,无论该许可与否。没有明显的方法允许setAccessible修改某些字段/方法而不允许其他字段/方法。但是,使用SecurityManager可以禁止类完全引用某些包,无论有无反射。

无论SecurityManager的配置如何,我都可以编写可设置setAccessible-proof的类吗?…还是由谁来管理配置?

你不能,而且你当然可以。

2020-03-23