谈谈JDK 漏洞 6260652

JDK 漏洞 6260652

在看ArrayList源码的过程中 发现带参构造里有一个注释:

// c.toArray might (incorrectly) not return Object[] (see 6260652)

java 复制代码
 public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

于是带着下面三个问题去查资料,探究了一下这个问题。

这段代码解决的是什么漏洞?什么情况下会出现这个漏洞呢? 这个漏洞可能会引发哪些问题?

先查官网的bug库
漏洞在官网上的描述:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652

问题提出时间:2005-04-25 解决时间: 2015-07-09 , 受到影响的版本JDK5.0 和 6

在JDK9中修复了该问题。

这里的问题描述说的是:

JDK1.5.0_02版本中、 Collection接口的 <T> T[] toArray(T[] a);方法上有这么一段注释

java 复制代码
Note that toArray(new Object[0])  is identical in function to toArray() 
意思是   toArray(new Object[0]) 方法和  toArray() 方法在功能上是相同的

我们看下这两个方法在Collection接口中的定义:

java 复制代码
Object[] toArray();

<T> T[] toArray(T[] a);

我们知道接口是属于比较高层次的抽象,任何实现了某个接口的类都必须遵循这个接口中方法的声明。

也就是说 Collection接口的子类 都必须遵循上面 toArray(new Object[0]) 方法和 toArray() 方法在功能上是相同的这个声明。

但是,但是来了就是问题要来了。县长来了,鹅城就太平了。

Arrays类来了JDK的坑就有了。。。

之前其实已经提过Arrays.asList方法返回的List 实际上是 Arrays类的一个静态内部类 private static class ArrayList<E>

并非 java.util.ArrayList ,所以之前在 **Java API使用避坑合集**这篇文章中介绍过,private static class ArrayList 不支持添加或者修改操作。否则会报java.lang.UnsupportedOperationException。

那么我们现在说的这个漏洞又是Arrays搞得鬼,这真验证了那句话,约80%的程序错误可能源于20%的代码模块(2/8定律,又称为帕累托原则(Pareto Principle))。

我们回到 JDK 漏洞 6260652的讨论,上面说了 private static class ArrayList 坑, 那么 JDK 漏洞 6260652 是具体怎么体现的呢?

实际上就是 private static class ArrayList 这个静态内部类, 没有遵循Collection 接口声明的 这句话Note that toArray(new Object[0]) is identical in function to toArray()

我们看下private static class ArrayList中 toArray的具体实现 下面源码是 JDK8版本

java 复制代码
// 无参的toArray 实际上调用的 是clone方法  返回的是Object数组 (但是返回的数组实际的类型 并一定是Object)
@Override
public Object[] toArray() {
   return a.clone();
}

// 有参的toArray 实际上调用的是 Arrays.copyOf  返回的数组类型是根据具体的运行时类型而确定的
// 所以如果该方法返回了 Object[]   上面的toArray()返回了 String[]  那么两个toArray方法返回的数组类型就不一致了  
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
    int size = size();
    if (a.length < size)
        return Arrays.copyOf(this.a, size,
                             (Class<? extends T[]>) a.getClass());
    System.arraycopy(this.a, 0, a, 0, size);
    if (a.length > size)
        a[size] = null;
    return a;
}

这两个方法在某些情况下 返回的结果并不一致。也就不遵循Collection 接口对这两个方法的声明。

看下面的代码:

java 复制代码
public class TestA {
    public static void main(String[] args) {
        
       List l = Arrays.asList(args);
        
        Object[] objects1 = l.toArray();
        Object[] objects2 = l.toArray(new Object[0]);
        
        System.out.println(objects1.getClass());
        System.out.println(objects2.getClass());

    }
}

运行结果:

java 复制代码
class [Ljava.lang.String;
class [Ljava.lang.Object;

很明显同一个 private static class ArrayList 对象调用toArray()和toArray(new Object[0])两个方法,返回的数组类型不一致,

这和 Collection 接口声明的 这句话Note that toArray(new Object[0]) is identical in function to toArray() 相悖了。

这就是这个漏洞的具体表现。

这个漏洞可能会造成的问题如下:

java 复制代码
public static void main(String[] args) {

        List<String> list = Arrays.asList("a","b");
        System.out.println(list.getClass());
        Object[] o = list.toArray();
        System.out.println(o.getClass());
        o[0] = "c";
        System.out.println(Arrays.toString(o));
        
        // ArrayStoreException
        o[1] = new Object();

    }

运行结果:

shell 复制代码
class java.util.Arrays$ArrayList
class [Ljava.lang.String;
[c, b]
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Object

o[1] = new Object();报错了 ArrayStoreException

因为 java.util.Arrays$ArrayList 也就是 Arrays的静态内部类 private static class ArrayList 的toArray()方法返回了 Object数组,但是这个数组实际上的类型是String。

如果这个时候我们给 toArray()方法返回的数组中的元素赋值一个Object类型的变量,显然在编译时期可以正常编译,但是在运行时期就会出现Object对象 向String数组中存储的情况,此时JVM会抛出 ArrayStoreException。

我们再回头看真正的 java.util.ArrayList 集合中 的这段代码的时候就清楚了:

所以这个判断是为了保证真正存储集合元素的数组类型是 Object类型。

如果没有这段代码 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class);

我们通过反射做点小手脚,运行下面这段代码:

java 复制代码
public static void main(String[] args) throws Exception {

        // 创建一个普通的 ArrayList
        ArrayList<Object> list = new ArrayList<>(Arrays.asList("1", "2", "3"));

        // 使用反射获取 ArrayList 类中的 elementData 字段
        Field elementDataField = ArrayList.class.getDeclaredField("elementData");
        elementDataField.setAccessible(true);

        // 将 list对象的elementData 字段设置为一个新的数组  相当于 在ArrayList有参构造中执行了elementData = c.toArray();
        // 未执行  if (elementData.getClass() != Object[].class)    elementData = Arrays.copyOf(elementData, size, Object[].class);
        Object[] objects = Arrays.asList("1", "2", "3").toArray();
        elementDataField.set(list, objects);

        System.out.println(list);  // 输出: [1, 2, 3]

        System.out.println(objects.getClass());  // 输出 class [Ljava.lang.String;

        // 下面这行代码会抛出 ArrayStoreException
        list.add(new Object());
    }

可以看到在 list.add(new Object());时抛了 java.lang.ArrayStoreException 因为在运行时 ArrayList尝试把Object类型的对象添加进 String类型的数组中 这是不被允许的。

那么再回过头来看:

java 复制代码
 // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);

目的就很明确了, 由于运行时 c.toArray返回的可能不是 Object类型的数组 ,为了保证集合运行时的类型安全

通过elementData = Arrays.copyOf(elementData, size, Object[].class); 来保证 elementData 获取的是Object数组类型。

在JDK9版本,对 private static class ArrayList 的toArray()方法也进行了改进:

JDK9之前的版本是这样的:

java 复制代码
 @Override
public Object[] toArray() {
 	   // clone返回的是 运行时的具体类型
      return a.clone();
  }

JDK9及之后的版本是这样的:

java 复制代码
 @Override
 public Object[] toArray() {
			// 利用Arrays.copyOf 复制一个新的 Object类型数组
            return Arrays.copyOf(this.a, this.a.length, Object[].class);
      }
相关推荐
duration~29 分钟前
Maven随笔
java·maven
zmgst32 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
跃ZHD41 分钟前
前后端分离,Jackson,Long精度丢失
java
blammmp1 小时前
Java:数据结构-枚举
java·开发语言·数据结构
暗黑起源喵1 小时前
设计模式-工厂设计模式
java·开发语言·设计模式
WaaTong1 小时前
Java反射
java·开发语言·反射
九圣残炎2 小时前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge2 小时前
Netty篇(入门编程)
java·linux·服务器
Re.不晚2 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
雷神乐乐3 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven