changeset 378:cb58b31c450e

Fixed gifar vulnereability
author Jiri Vanek <jvanek@redhat.com>
date Wed, 10 Apr 2013 18:28:45 +0200
parents 94234beee2e9
children 45a5c6e2e368
files ChangeLog NEWS netx/net/sourceforge/jnlp/Launcher.java netx/net/sourceforge/jnlp/resources/Messages.properties netx/net/sourceforge/jnlp/runtime/Boot.java netx/net/sourceforge/jnlp/runtime/CachedJarFileCallback.java netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java netx/net/sourceforge/jnlp/runtime/JNLPRuntime.java netx/net/sourceforge/jnlp/util/InvalidJarHeaderException.java netx/net/sourceforge/jnlp/util/JarFile.java
diffstat 10 files changed, 261 insertions(+), 12 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Wed Apr 10 15:39:04 2013 +0200
+++ b/ChangeLog	Wed Apr 10 18:28:45 2013 +0200
@@ -1,3 +1,25 @@
+2013-04-10  Jiri Vanek <jvanek@redhat.com>
+
+	Fixed gifar vulnereability with automated testcase
+	* netx/net/sourceforge/jnlp/util/JarFile.java: IcedTea-Web replacement for 
+	java.util.jar.JarFile.java with capability to verify if the jar starts as jar
+	and not as something else (eg gif)
+	* netx/net/sourceforge/jnlp/Launcher.java: migrated to new JarFile
+	* netx/net/sourceforge/jnlp/resources/Messages.properties: added
+	BXignoreheaders key with description to new -Xignoreheaders switch
+	* netx/net/sourceforge/jnlp/runtime/Boot.java: added switch Xignoreheaders
+	to allow to disable the header verification.
+	* netx/net/sourceforge/jnlp/runtime/CachedJarFileCallback.java:
+	migrated to new JarFile
+	* netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java: improved
+	reporting of  new JarFile exceptions
+	* netx/net/sourceforge/jnlp/runtime/JNLPRuntime.java: new field 
+	ignoreHeaders, informing about new JarFile whether to verify or not verify
+	headers. By default verifying, so have value of false. 
+	* netx/net/sourceforge/jnlp/tools/JarCertVerifier.java: migrated to new JarFile
+	* netx/net/sourceforge/jnlp/util/InvalidJarHeaderException.java: new
+	not-checked exception to signify that jar is corrupted on headers level. 
+
 2013-04-10  Jiri Vanek <jvanek@redhat.com>
 
 	Fixed news
--- a/NEWS	Wed Apr 10 15:39:04 2013 +0200
+++ b/NEWS	Wed Apr 10 18:28:45 2013 +0200
@@ -9,6 +9,8 @@
 CVE-XXXX-YYYY: http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=XXXX-YYYY
 
 New in release 1.2.3 (2013-04-17):
+* Security Updates
+  - RH884705 - fixed gifar vulnereablity
 * Common
   - PR1161: X509VariableTrustManager does not work correctly with OpenJDK7
 * Plugin
--- a/netx/net/sourceforge/jnlp/Launcher.java	Wed Apr 10 15:39:04 2013 +0200
+++ b/netx/net/sourceforge/jnlp/Launcher.java	Wed Apr 10 18:28:45 2013 +0200
@@ -29,7 +29,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.jar.JarFile;
+import net.sourceforge.jnlp.util.JarFile;
 
 import net.sourceforge.jnlp.cache.CacheUtil;
 import net.sourceforge.jnlp.cache.UpdatePolicy;
--- a/netx/net/sourceforge/jnlp/resources/Messages.properties	Wed Apr 10 15:39:04 2013 +0200
+++ b/netx/net/sourceforge/jnlp/resources/Messages.properties	Wed Apr 10 18:28:45 2013 +0200
@@ -93,7 +93,7 @@
 LNotVerified=Jars not verified.
 LCancelOnUserRequest=Canceled on user request.
 LFatalVerification=A fatal error occurred while trying to verify jars.
-LFatalVerificationInfo=
+LFatalVerificationInfo=Description
 
 LNotVerifiedDialog=Not all jars could be verified.
 LAskToContinue=Would you still like to continue running this application?
@@ -181,6 +181,7 @@
 BOViewer    = Shows the trusted certificate viewer.
 BXnofork    = Do not create another JVM.
 BXclearcache= Clean the JNLP application cache.
+BXignoreheaders= Skip jar header verification.
 BOHelp      = Print this message and exit.
 
 # Cache
--- a/netx/net/sourceforge/jnlp/runtime/Boot.java	Wed Apr 10 15:39:04 2013 +0200
+++ b/netx/net/sourceforge/jnlp/runtime/Boot.java	Wed Apr 10 18:28:45 2013 +0200
@@ -102,6 +102,7 @@
             + "  -strict               " + R("BOStrict") + "\n"
             + "  -Xnofork              " + R("BXnofork") + "\n"
             + "  -Xclearcache          " + R("BXclearcache") + "\n"
+            + "  -Xignoreheaders       " + R("BXignoreheaders") + "\n"
             + "  -help                 " + R("BOHelp") + "\n";
 
     private static final String doubleArgs = "-basedir -jnlp -arg -param -property -update";
@@ -159,6 +160,9 @@
         if (null != getOption("-Xtrustall")) {
             JNLPRuntime.setTrustAll(true);
         }
+        if (null != getOption("-Xignoreheaders")) {
+            JNLPRuntime.setIgnoreHeaders(true);
+        }
 
         JNLPRuntime.setInitialArgments(Arrays.asList(argsIn));
 
--- a/netx/net/sourceforge/jnlp/runtime/CachedJarFileCallback.java	Wed Apr 10 15:39:04 2013 +0200
+++ b/netx/net/sourceforge/jnlp/runtime/CachedJarFileCallback.java	Wed Apr 10 18:28:45 2013 +0200
@@ -49,7 +49,7 @@
 import java.security.PrivilegedExceptionAction;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.jar.JarFile;
+import net.sourceforge.jnlp.util.JarFile;
 
 import net.sourceforge.jnlp.util.UrlUtils;
 
@@ -81,7 +81,7 @@
     }
 
     @Override
-    public JarFile retrieve(URL url) throws IOException {
+    public java.util.jar.JarFile retrieve(URL url) throws IOException {
         URL localUrl = mapping.get(url);
 
         if (localUrl == null) {
@@ -122,8 +122,8 @@
     /*
      * This method is a copy of URLJarFile.retrieve() without the callback check.
      */
-    private JarFile cacheJarFile(URL url) throws IOException {
-        JarFile result = null;
+    private  java.util.jar.JarFile cacheJarFile(URL url) throws IOException {
+        java.util.jar.JarFile result = null;
 
         final int BUF_SIZE = 2048;
 
@@ -132,9 +132,9 @@
 
         try {
             result =
-                    AccessController.doPrivileged(new PrivilegedExceptionAction<JarFile>() {
+                    AccessController.doPrivileged(new PrivilegedExceptionAction<java.util.jar.JarFile>() {
                         @Override
-                        public JarFile run() throws IOException {
+                        public java.util.jar.JarFile run() throws IOException {
                             OutputStream out = null;
                             File tmpFile = null;
                             try {
--- a/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Wed Apr 10 15:39:04 2013 +0200
+++ b/netx/net/sourceforge/jnlp/runtime/JNLPClassLoader.java	Wed Apr 10 18:28:45 2013 +0200
@@ -50,7 +50,7 @@
 import java.util.Vector;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
+import net.sourceforge.jnlp.util.JarFile;
 import java.util.jar.Manifest;
 
 import net.sourceforge.jnlp.AppletDesc;
@@ -483,7 +483,7 @@
                 //to read the cacerts or trusted.certs files.
                 e.printStackTrace();
                 throw new LaunchException(null, null, R("LSFatal"),
-                                        R("LCInit"), R("LFatalVerification"), R("LFatalVerificationInfo"));
+                                        R("LCInit"), R("LFatalVerification"), R("LFatalVerificationInfo") + ": " +e.getMessage());
             }
 
             //Case when at least one jar has some signing
--- a/netx/net/sourceforge/jnlp/runtime/JNLPRuntime.java	Wed Apr 10 15:39:04 2013 +0200
+++ b/netx/net/sourceforge/jnlp/runtime/JNLPRuntime.java	Wed Apr 10 18:28:45 2013 +0200
@@ -88,7 +88,7 @@
  * @version $Revision: 1.19 $
  */
 public class JNLPRuntime {
-
+    
     static {
         loadResources();
     }
@@ -146,8 +146,16 @@
     /** set to false to indicate another JVM should not be spawned, even if necessary */
     private static boolean forksAllowed = true;
 
-    /** all security dialogs will be consumed and pretented as beeing verified by user and allowed.*/
+    /** all security dialogs will be consumed and pretented as being verified by user and allowed.*/
     private static boolean trustAll=false;
+    /** 
+     * Header is not checked and so eg. gifar exploit is possible
+     * @see http://en.wikipedia.org/wiki/Gifar for this kind of attack.
+     * However if jar file is a bit corrupted, then it sometimes can work so 
+     * this switch can disable the header check.
+     * 
+     */
+    private static boolean ignoreHeaders=false;
 
     /** contains the arguments passed to the jnlp runtime */
     private static List<String> initialArguments;
@@ -816,4 +824,14 @@
         return trustAll;
     }
 
+    public static boolean isIgnoreHeaders() {
+        return ignoreHeaders;
+    }
+
+    public static void setIgnoreHeaders(boolean ignoreHeaders) {
+        JNLPRuntime.ignoreHeaders = ignoreHeaders;
+    }
+    
+    
+
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/util/InvalidJarHeaderException.java	Wed Apr 10 18:28:45 2013 +0200
@@ -0,0 +1,49 @@
+/*
+Copyright (C) 2012 Red Hat, Inc.
+
+This file is part of IcedTea.
+
+IcedTea is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+IcedTea is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with IcedTea; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version. */
+package net.sourceforge.jnlp.util;
+
+/**
+ * Thrown from net.sourceforge.jnlp.utilJArFile when verification of headers fails
+ * 
+ */
+public class InvalidJarHeaderException extends RuntimeException{
+
+    public InvalidJarHeaderException(String string) {
+        super(string);
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/util/JarFile.java	Wed Apr 10 18:28:45 2013 +0200
@@ -0,0 +1,153 @@
+/*
+ Copyright (C) 2012 Red Hat, Inc.
+
+ This file is part of IcedTea.
+
+ IcedTea is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ IcedTea is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with IcedTea; see the file COPYING.  If not, write to the
+ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301 USA.
+
+ Linking this library statically or dynamically with other modules is
+ making a combined work based on this library.  Thus, the terms and
+ conditions of the GNU General Public License cover the whole
+ combination.
+
+ As a special exception, the copyright holders of this library give you
+ permission to link this library with independent modules to produce an
+ executable, regardless of the license terms of these independent
+ modules, and to copy and distribute the resulting executable under
+ terms of your choice, provided that you also meet, for each linked
+ independent module, the terms and conditions of the license of that
+ module.  An independent module is a module which is not derived from
+ or based on this library.  If you modify this library, you may extend
+ this exception to your version of the library, but you are not
+ obligated to do so.  If you do not wish to do so, delete this
+ exception statement from your version. */
+package net.sourceforge.jnlp.util;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipFile;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+
+public class JarFile extends java.util.jar.JarFile {
+
+    public JarFile(String name) throws IOException {
+       super(name);
+       verifyZipHeader(new File(name));
+    }
+
+    /**
+     */
+    public JarFile(String name, boolean verify) throws IOException {
+        super(name, verify);
+        verifyZipHeader(new File(name));
+    }
+
+    /**
+     */
+    public JarFile(File file) throws IOException {
+        super(file);
+        verifyZipHeader(file);
+    }
+
+    /**
+     */
+    public JarFile(File file, boolean verify) throws IOException {
+        super(file, verify);
+        verifyZipHeader(file);
+    }
+
+    /*
+     */
+    public JarFile(File file, boolean verify, int mode) throws IOException {
+        super(file, verify, mode);
+         verifyZipHeader(file);
+    }
+    
+    
+    
+    
+    /**
+     * According to specification -
+     * http://www.pkware.com/documents/casestudies/APPNOTE.TXT or just google
+     * around zip header all entries in zip-compressed must start with well
+     * known "PK" which is defined as hexa x50 x4b x03 x04, which in decimal are
+     * 80 75 3 4.
+     * 
+     * Note - this is not file-header, it is item-header.
+     *
+     * Actually most of compressing formats have some n-bytes header se eg:
+     * http://www.gzip.org/zlib/rfc-gzip.html#header-trailer for ID1 and ID2 so
+     * in case that some differently compressed jars will come to play, this is
+     * the palce where to fix it.
+     *
+     */
+    private static final byte[] ZIP_LOCAL_FILE_HEADER_SIGNATURE = new byte[]{80, 75, 3, 4};
+
+    /**
+     * This method is checking first four bytes of jar-file against
+     * ZIP_LOCAL_FILE_HEADER_SIGNATURE
+     *
+     * Although zip specification allows to skip all corrupted entries, it is
+     * not safe for jars. If first four bytes of file are not zip
+     * ZIP_LOCAL_FILE_HEADER_SIGNATURE then exception is thrown
+     * 
+     * As noted, ZIP_LOCAL_FILE_HEADER_SIGNATURE is not ile-header, but is item-header.
+     * Possible attack is using the fact that entries without header are considered
+     * corrupted and so can be ignoered. However, for other they can have some meaning.
+     * 
+     * So for our purposes we must insists on first record to be valid.
+     *
+     * @param file
+     * @throws IOException, InvalidJarHeaderException
+     */
+    public static void verifyZipHeader(File file) throws IOException {
+        if (!JNLPRuntime.isIgnoreHeaders()) {
+            InputStream s = new FileInputStream(file);
+            try {
+                byte[] buffer = new byte[ZIP_LOCAL_FILE_HEADER_SIGNATURE.length];
+                /*
+                 * for case that new byte[] will accidently initialize same
+                 * sequence as zip header and during the read the buffer will not be filled
+                 */                
+                for (int i = 0; i < buffer.length; i++) {
+                    buffer[i] = 0;
+                }
+                int toRead = ZIP_LOCAL_FILE_HEADER_SIGNATURE.length;
+                int readSoFar = 0;
+                int n = 0;
+                /*
+                 * this is used instead of s.read(buffer) for case of block and
+                 * so returned not-fully-filled dbuffer
+                 */ 
+                while ((n = s.read(buffer, readSoFar, buffer.length - readSoFar)) != -1) {
+                    readSoFar += n;
+                    if (readSoFar == toRead) {
+                        break;
+                    }
+                }
+                for (int i = 0; i < buffer.length; i++) {
+                    if (buffer[i] != ZIP_LOCAL_FILE_HEADER_SIGNATURE[i]) {
+                        throw new InvalidJarHeaderException("Jar " + file.getName() + " do not heave valid header. You can skip this check by -Xignoreheaders");
+                    }
+                }
+            } finally {
+                s.close();
+            }
+        }
+    }
+}