6966692: defaultReadObject can set a field multiple times
authorskoppar
Tue, 28 Sep 2010 01:13:22 -0700
changeset 7031 d77ff2048ad5
parent 7030 90cf66131063
child 7032 27e00bbc50fc
child 7034 a31042b569d0
6966692: defaultReadObject can set a field multiple times Reviewed-by: hawtin
jdk/src/share/classes/java/io/ObjectStreamClass.java
jdk/test/java/io/Serializable/6966692/Attack.java
jdk/test/java/io/Serializable/6966692/README
jdk/test/java/io/Serializable/6966692/Test6966692.sh
jdk/test/java/io/Serializable/6966692/Victim.java
--- a/jdk/src/share/classes/java/io/ObjectStreamClass.java	Tue Sep 28 01:09:10 2010 -0700
+++ b/jdk/src/share/classes/java/io/ObjectStreamClass.java	Tue Sep 28 01:13:22 2010 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1996, 2008, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1996, 2010, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -1830,8 +1830,10 @@
         private final ObjectStreamField[] fields;
         /** number of primitive fields */
         private final int numPrimFields;
-        /** unsafe field keys */
-        private final long[] keys;
+        /** unsafe field keys for reading fields - may contain dupes */
+        private final long[] readKeys;
+        /** unsafe fields keys for writing fields - no dupes */
+        private final long[] writeKeys;
         /** field data offsets */
         private final int[] offsets;
         /** field type codes */
@@ -1849,16 +1851,22 @@
         FieldReflector(ObjectStreamField[] fields) {
             this.fields = fields;
             int nfields = fields.length;
-            keys = new long[nfields];
+            readKeys = new long[nfields];
+            writeKeys = new long[nfields];
             offsets = new int[nfields];
             typeCodes = new char[nfields];
             ArrayList<Class<?>> typeList = new ArrayList<Class<?>>();
+            Set<Long> usedKeys = new HashSet<Long>();
+
 
             for (int i = 0; i < nfields; i++) {
                 ObjectStreamField f = fields[i];
                 Field rf = f.getField();
-                keys[i] = (rf != null) ?
+                long key = (rf != null) ?
                     unsafe.objectFieldOffset(rf) : Unsafe.INVALID_FIELD_OFFSET;
+                readKeys[i] = key;
+                writeKeys[i] = usedKeys.add(key) ?
+                    key : Unsafe.INVALID_FIELD_OFFSET;
                 offsets[i] = f.getOffset();
                 typeCodes[i] = f.getTypeCode();
                 if (!f.isPrimitive()) {
@@ -1894,7 +1902,7 @@
              * in array should be equal to Unsafe.INVALID_FIELD_OFFSET.
              */
             for (int i = 0; i < numPrimFields; i++) {
-                long key = keys[i];
+                long key = readKeys[i];
                 int off = offsets[i];
                 switch (typeCodes[i]) {
                     case 'Z':
@@ -1945,7 +1953,7 @@
                 throw new NullPointerException();
             }
             for (int i = 0; i < numPrimFields; i++) {
-                long key = keys[i];
+                long key = writeKeys[i];
                 if (key == Unsafe.INVALID_FIELD_OFFSET) {
                     continue;           // discard value
                 }
@@ -2006,7 +2014,7 @@
                 switch (typeCodes[i]) {
                     case 'L':
                     case '[':
-                        vals[offsets[i]] = unsafe.getObject(obj, keys[i]);
+                        vals[offsets[i]] = unsafe.getObject(obj, readKeys[i]);
                         break;
 
                     default:
@@ -2027,7 +2035,7 @@
                 throw new NullPointerException();
             }
             for (int i = numPrimFields; i < fields.length; i++) {
-                long key = keys[i];
+                long key = writeKeys[i];
                 if (key == Unsafe.INVALID_FIELD_OFFSET) {
                     continue;           // discard value
                 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/Serializable/6966692/Attack.java	Tue Sep 28 01:13:22 2010 -0700
@@ -0,0 +1,34 @@
+/*
+ * @test
+ * @bug 6966692
+ * @summary  defaultReadObject can set a field multiple times
+ * @run shell Test6966692.sh
+*/
+
+import java.io.*;
+
+public class Attack {
+    public static void main(String[] args) throws Exception {
+        attack(setup());
+    }
+    /** Returned data has Victim with two aaaa fields. */
+    private static byte[] setup() throws Exception {
+        Victim victim = new Victim();
+        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
+        ObjectOutputStream out = new ObjectOutputStream(byteOut);
+        out.writeObject(victim);
+        out.close();
+        byte[] data = byteOut.toByteArray();
+        String str = new String(data, 0); // hibyte is 0
+        str = str.replaceAll("bbbb", "aaaa");
+        str.getBytes(0, data.length, data, 0); // ignore hibyte
+        return data;
+    }
+    private static void attack(byte[] data) throws Exception {
+        ObjectInputStream in = new ObjectInputStream(
+            new ByteArrayInputStream(data)
+        );
+        Victim victim = (Victim)in.readObject();
+        System.out.println(victim+" "+victim.aaaa);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/Serializable/6966692/README	Tue Sep 28 01:13:22 2010 -0700
@@ -0,0 +1,23 @@
+Testcase shows default deserialisation of the Victim having two values for the same field. 
+
+Probably requires dual core to run successfully. 
+
+Reading thread is warmed up so that it can easily win the race for the demonstration, but this means we need to make the field volatile.
+
+Typical output:
+
+Victim@1551f60 BBBB
+Victim@1551f60 AAAA
+
+The output when its fixed is, 
+Victim@1975b59 AAAA
+Victim@1975b59 AAAA  - The value is retained
+
+and when it is not fixed, it shows something like 
+Victim@173a10f AAAA
+Victim@173a10f BBBB - the value of the object gets set again and hence is different. This is a bug
+
+Look at the 
+AAAA AAAA 
+and 
+AAAA BBBB
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/Serializable/6966692/Test6966692.sh	Tue Sep 28 01:13:22 2010 -0700
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+if [ "${TESTSRC}" = "" ]
+then TESTSRC=.
+fi
+
+if [ "${TESTJAVA}" = "" ]
+then
+  PARENT=`dirname \`which java\``
+  TESTJAVA=`dirname ${PARENT}`
+  echo "TESTJAVA not set, selecting " ${TESTJAVA}
+  echo "If this is incorrect, try setting the variable manually."
+fi
+
+if [ "${TESTCLASSES}" = "" ]
+then
+  echo "TESTCLASSES not set.  Test cannot execute.  Failed."
+  exit 1
+fi
+
+BIT_FLAG=""
+
+# set platform-dependent variables
+OS=`uname -s`
+case "$OS" in
+  SunOS | Linux )
+    NULL=/dev/null
+    PS=":"
+    FS="/"
+    ## for solaris, linux it's HOME
+    FILE_LOCATION=$HOME
+    if [ -f ${FILE_LOCATION}${FS}JDK64BIT -a ${OS} = "SunOS" ]
+    then
+        BIT_FLAG=`cat ${FILE_LOCATION}${FS}JDK64BIT | grep -v '^#'`
+    fi
+    ;;
+  Windows_* )
+    NULL=NUL
+    PS=";"
+    FS="\\"
+    ;;
+  * )
+    echo "Unrecognized system!"
+    exit 1;
+    ;;
+esac
+
+JEMMYPATH=${CPAPPEND}
+CLASSPATH=.${PS}${TESTCLASSES}${PS}${JEMMYPATH} ; export CLASSPATH
+
+THIS_DIR=`pwd`
+
+${TESTJAVA}${FS}bin${FS}java ${BIT_FLAG} -version
+
+cp ${TESTSRC}${FS}*.java .
+chmod 777 *.java
+
+${TESTJAVA}${FS}bin${FS}javac *.java
+
+${TESTJAVA}${FS}bin${FS}java ${BIT_FLAG} Attack > test.out 2>&1
+
+cat test.out
+
+STATUS=0
+
+egrep "^Victim.*BBBB.*AAAA|^Victim.*AAAA.*BBBB" test.out
+
+if [ $? = 0 ]
+then
+    STATUS=1
+else
+    egrep "^Victim.*BBBB.*BBBB|^Victim.*AAAA.*AAAA" test.out
+
+    if [ $? != 0 ]; then
+        STATUS=1
+    fi
+fi
+
+exit $STATUS
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/Serializable/6966692/Victim.java	Tue Sep 28 01:13:22 2010 -0700
@@ -0,0 +1,35 @@
+import java.io.*;
+
+public class Victim implements Serializable {
+    public volatile Object aaaa = "AAAA"; // must be volatile...
+    private final Object aabb = new Show(this);
+    public Object bbbb = "BBBB";
+}
+class Show implements Serializable {
+    private final Victim victim;
+    public Show(Victim victim) {
+        this.victim = victim;
+    }
+    private void readObject(java.io.ObjectInputStream in)
+     throws IOException, ClassNotFoundException
+    {
+        in.defaultReadObject();
+        Thread thread = new Thread(new Runnable() { public void run() {
+            for (;;) {
+                Object a = victim.aaaa;
+                if (a != null) {
+                    System.err.println(victim+" "+a);
+                    break;
+                }
+            }
+        }});
+        thread.start();
+
+        // Make sure we are running compiled whilst serialisation is done interpreted.
+        try {
+            Thread.sleep(1000);
+        } catch (java.lang.InterruptedException exc) {
+            Thread.currentThread().interrupt();
+        }
+    }
+}