Java字符串不是真正不可变的含义是什么?

时间:2014-10-07 21:12:42

标签: java string security clone immutability

背景

在Java 101中,我们被教导:

  

String是不可变的。

是。好。感谢。

然后我们进入Java 102(或者Java 201),我们发现:

  

String并非真正不可变:您可以使用反射来更改它。

阿。精细。根据你的观点,要么非常可爱,要么无法估量。

到目前为止,这些事情在Stack Overflow和其他地方已经discussed ad infinitum。在构思这个问题时,我认为这是理所当然的。

我有兴趣问的是:

问题

  

一旦我们发现String不是真正不可变的,那么我们应该如何编写代码会有什么影响?

让我在两个方面更具体。

JVM沙盒

恶意类是否有可能使用此技巧来破坏JVM的安全性,并开始尝试不应该使用它?例如,JDK中是否存在从方法返回String的位置,并且只有在无法更改它的情况下它才安全?

这是一个很有希望的开始:

    String prop = "java.version";
    // retrieve a System property as a String
    String s = System.getProperty(prop);
    System.out.println(s);
    // now mess with it
    Field field = String.class.getDeclaredField("value");  
    field.setAccessible(true);  
    char[] value = (char[])field.get(s);  
    value[0] = 'x';
    //turns out we've changed not just the String we were
    //given but the underlying property too!
    System.out.println(System.getProperty(prop));

这样做是从JVM中检索系统属性,该属性以String形式返回,然后更改String;结果是基础财产也发生了变化。这可以用来造成严重破坏吗?

我不确定。值得注意的是,您必须拥有执行反射的权限。那个安全游戏已经上升了吗?它允许我在这里获得更改安全属性的权限,但这是预期的,还是一个问题?

有没有办法延长这个做更糟糕的事情?

防御性克隆

我们总是被告知,在将数组和其他对象传递给可能修改它们的方法之前克隆它们是个好主意,除非我们希望它们被修改。这只是很好的编码实践。如果你给某人一个阵列的唯一副本并且它回来搞砸,这是你自己的愚蠢错误。

但看起来同样的论点应该适用于String!我从来没有听过有人说过我们应该克隆一个String,然后再把它传递给别人写的方法。但是,如果String像数组一样可变,那么这有什么不同呢?

支持防御性克隆字符串的争论

如果防御性克隆完全不知道方法可以对我们传递的东西做什么,如果我们传递的东西是可变的,那么我们应该克隆它。如果代码可能是恶意的,那么它可能会改变String;因此,只要String保持不变,重要的是我们应该确保发送副本而不是真实内容。

如果我们不相信它不会对String做坏事,那么不应该说不受信任的代码应该在安全管理器下运行。如果String的情况属实,那么数组就是如此;但是没有人说只有在你用安全管理器锁定代码的情况下才能克隆数组和其他对象。

反对防守克隆字符串的争论

数组可能只是通过草率编码来修改;换句话说,它可能会被无意中修改。但没有人无意间改变String:只能用偷偷摸摸的技巧来表达程序员试图打破不变性。

这使得两种情况不同。我们真的不应该将数组(甚至是它的克隆)传递给我们在任何级别上都不信任的代码。你没有从某个地方下载一个库,然后认为你没事,因为你克隆了你的数组。你在防御性方面克隆是因为你认为程序员可能会犯错误或对你发送的数据做出不同的假设。如果您担心代码可能会在后面修改String,那么您根本不应该运行代码。克隆String所实现的只是性能开销。

答案?

我们应该如何看待这个?使用这些技巧可以打破JVM沙盒吗?我们应该根据String的可变性进行防御性编码,还是全部是红鲱鱼?

2 个答案:

答案 0 :(得分:4)

可以为Java程序启用安全设置。根据{{​​3}},

  

首先,如果有安全管理器,则使用ReflectPermission(" suppressAccessChecks")权限调用其checkPermission方法。

     

如果flag为true,则引发SecurityException,但可能不会更改此对象的可访问性(例如,如果此元素对象是类Class的Constructor对象)。

     

如果此对象是类java.lang.Class的Constructor对象,并且flag为true,则引发SecurityException。

因此,如果SecurityManager不允许进行此项检查,则可以阻止任何代码成功调用setAccessible,从而保留String s的不变性。

答案 1 :(得分:3)

Java有适当的机制来防止这种情况发生。具体来说,例如,为了破坏字符串,您必须致电field.setAccessible(true);,这对您而言有效,但如果您查看文档,则setAccessible(true)电话可以throw SecurityException

Java允许您安装或修改可以锁定这些反射API的安全管理器。例如,考虑Ideone,它允许您运行有限的Java应用程序。在很大程度上,他们通过限制系统的安全权限来做到这一点。

您应该阅读the SecuityManager以及trail document

您还应该通读