关于java:如何将setAccessible限制为仅“合法”使用?

How to limit setAccessible to only “legitimate” uses?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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"
   }
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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吗?

      • 它是如何工作的?白名单/黑名单,粒度等?
      • 必须在您的应用程序中对其进行配置是否常见?
    • 不论SecurityManager配置如何,我都可以编写符合setAccessible标准的类吗?

      • 还是由谁来管理配置?

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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!"
   }
}

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


我需要为此担心吗???

好。

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

好。

如果您要向世界各地的人们分发名为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的类吗? ...还是由谁来管理配置?

好。

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

好。

好。


  • What are the truly legitimate uses for setAccessible?

单元测试,JVM内部(例如实现System.setError(...))等等。

  • Could Java has been designed as to NOT have this need in the first place?
  • What would the negative consequences (if any) of such design be?

很多事情将无法实现。例如,各种Java持久性,序列化和依赖项注入都依赖于反射。几乎所有在运行时都依赖JavaBeans约定的东西。

  • Can you restrict setAccessible to legitimate uses only?
  • Is it only through SecurityManager?

是。

  • How does it work? Whitelist/blacklist, granularity, etc?

这取决于权限,但是我相信使用setAccessible的权限是二进制的。如果需要粒度,则需要对要限制的类使用其他类加载器和其他安全管理器。我猜您可以实现一个实现更细粒度逻辑的自定义安全管理器。

  • Is it common to have to configure it in your applications?

没有。

  • Can I write my classes to be setAccessible-proof regardless of SecurityManager configuration?

    • Or am I at the mercy of whoever manages the configuration?

不,你不能,是的。

另一种选择是通过源代码分析工具"强制"执行此操作。例如自定义pmdfindbugs规则。或对(例如)grep setAccessible ...标识的代码进行选择性代码审查。

作为对后续行动的回应

None of my classes have any semblance of enforceable privacy what-so-ever. The singleton pattern (putting doubts about its merits aside) is now impossible to enforce.

如果那让您担心,那么我想您需要担心。但是实际上您不应该尝试迫使其他程序员尊重您的设计决策。如果它们足够愚蠢,可以无偿地创建多个单例实例(例如),那么它们可以承受后果。

而且,如果您的意思是"隐私"包含保护敏感信息免于泄露的含义,那么您也将树立错误的树。保护敏感数据的方法是不允许不信任的代码进入处理敏感数据的安全沙箱中。 Java访问修饰符并非旨在成为一种安全机制。

- Am I the only one who thinks this is a HUGE concern?

可能不是唯一的一个:-)。但是海事组织,这不是一个问题。公认的事实是,不应在沙箱中执行不受信任的代码。如果您有受信任的代码/受信任的程序员从事此类工作,那么与意外地可变字符串相比,您的问题会更糟。 (想想逻辑炸弹...)


在这种情况下,反射确实与安全/保障正交。

我们如何限制反射?

Java具有安全管理器和ClassLoader作为其安全模型的基础。对于您的情况,我想您需要查看java.lang.reflect.ReflectPermission

但这并不能完全解决反射问题。可用的反射功能应遵循细粒度的授权方案,而现在情况并非如此。例如。以允许某些框架使用反射(例如Hibernate),但其余代码则不允许。或者为了调试目的而允许程序仅以只读方式反映。

将来可能成为主流的一种方法是使用所谓的镜子将反射功能与类分开。请参阅镜像:元级别设施的设计原则。但是,还有其他各种研究可以解决这个问题。但是我同意动态语言的问题比静态语言更为严重。

我们应该担心反射给我们带来的超级大国吗?是的,没有。

是的,因为Java平台应该由ClassLoader和安全管理器保护。破坏反射的能力可以看作是一种突破。

从某种意义上说,大多数系统都不是完全安全的。很多类经常可以被子类化,您可能已经就这样滥用了系统。当然,可以将类做成final或密封,以使它们不能在其他jar中子类化。但是据此,只有少数几个类得到正确保护(例如,字符串)。

有关最终课程的详细信息,请参见此答案。另请参阅Sami Koivu的博客,以获取有关安全性的更多Java技巧。

Java的安全性模型在某些方面可以视为不足。诸如NewSpeak之类的某些语言甚至采用了更为激进的模块化方法,在这种方法中,您只能访问依赖关系反转显式提供给您的内容(默认情况下为无)。

同样重要的是要注意,安全性始终是相对的。在语言级别,例如,您不能阻止模块形式消耗100%的CPU或消耗所有内存,直到OutOfMemoryException。这些问题需要通过其他方式解决。将来我们可能会看到Java扩展了资源利用率配额,但是明天就不行了:)

我可以在这个问题上做更多的扩展,但是我认为我已经指出了。