谈谈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);
      }
相关推荐
浮游本尊1 小时前
Java学习第22天 - 云原生与容器化
java
渣哥3 小时前
原来 Java 里线程安全集合有这么多种
java
间彧3 小时前
Spring Boot集成Spring Security完整指南
java
间彧3 小时前
Spring Secutiy基本原理及工作流程
java
Java水解4 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆6 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学7 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole7 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端
华仔啊7 小时前
基于 RuoYi-Vue 轻松实现单用户登录功能,亲测有效
java·vue.js·后端