Vulnerability: Improper Control of Generation of Code in HawtJNI Race condition in hawtjni-runtime/src/main/java/org/fusesource/hawtjni/runtime/Library.java in HawtJNI before 1.8, when a custom library path is not specified, allows local users to execute arbitrary Java code by overwriting a temporary JAR file with a predictable name in /tmp.
Vulnerability ID: GHSA-49j7-qghp-5wj8
CWE: CWE-94
Severity score: 0
Project: https://github.com/fusesource/hawtjni
Published: 2022-05-17
File: hawtjni-runtime/src/main/java/org/fusesource/hawtjni/runtime/Library.java
Commit message: Simplify shared lib extraction.
Commit author: Hiram Chirino

Vulnerable version (before fix):
  *******************************************************************************/ package org.fusesource.hawtjni.runtime;  import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.regex.Pattern;  /** final public String getLibraryFileName() {     private boolean exractAndLoad(ArrayList<String> errors, String version, String customPath, String resourcePath) {         URL resource = classLoader.getResource(resourcePath);         if( resource !=null ) {                          String libName = name + "-" + getBitModel();             if( version !=null) {                 libName += "-" + version;             }                          if( customPath!=null ) {                 // Try to extract it to the custom path...                 File target = file(customPath, map(libName));                 if( extract(errors, resource, target) ) {                     if( load(errors, target) ) {                         return true;                     } private boolean exractAndLoad(ArrayList<String> errors, String version, String c                          // Fall back to extracting to the tmp dir             customPath = System.getProperty("java.io.tmpdir");             File target = file(customPath, map(libName));             if( extract(errors, resource, target) ) {                 if( load(errors, target) ) {                     return true;                 } private String map(String libName) {         return libName;     }      private boolean extract(ArrayList<String> errors, URL source, File target) {         FileOutputStream os = null;         InputStream is = null;         boolean extracting = false;         try {             if (!target.exists() || isStale(source, target) ) {                 is = source.openStream();                 if (is != null) {                     byte[] buffer = new byte[4096];                     os = new FileOutputStream(target);                     extracting = true;                     int read;                     while ((read = is.read(buffer)) != -1) {                         os.write(buffer, 0, read);                     }                     os.close();                     is.close();                     chmod("755", target);                 }             }         } catch (Throwable e) {             try {                 if (os != null)                     os.close();             } catch (IOException e1) {             }             try {                 if (is != null)                     is.close();             } catch (IOException e1) {             }             if (extracting && target.exists())                 target.delete();             errors.add(e.getMessage());             return false;         }         return true;     }      private boolean isStale(URL source, File target) {                  if( source.getProtocol().equals("jar") ) {             // unwrap the jar protocol...             try {                 String parts[] = source.getFile().split(Pattern.quote("!"));                 source = new URL(parts[0]);             } catch (MalformedURLException e) {                 return false;             }         }                  File sourceFile=null;         if( source.getProtocol().equals("file") ) {             sourceFile = new File(source.getFile());         }         if( sourceFile!=null && sourceFile.exists() ) {             if( sourceFile.lastModified() > target.lastModified() ) {                 return true;             }         }         return false;     }      private void chmod(String permision, File path) {

Fixed version (after fix):
  *******************************************************************************/ package org.fusesource.hawtjni.runtime;  import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Random; import java.util.regex.Pattern;  /** final public String getLibraryFileName() {     private boolean exractAndLoad(ArrayList<String> errors, String version, String customPath, String resourcePath) {         URL resource = classLoader.getResource(resourcePath);         if( resource !=null ) {              String libName = name + "-" + getBitModel();             if( version !=null) {                 libName += "-" + version;             }             String []libNameParts = map(libName).split("\\.");             String prefix = libNameParts[0]+"-";             String suffix = "."+libNameParts[1];              if( customPath!=null ) {                 // Try to extract it to the custom path...                 File target = extract(errors, resource, prefix, suffix, file(customPath));                 if( target!=null ) {                     if( load(errors, target) ) {                         return true;                     } private boolean exractAndLoad(ArrayList<String> errors, String version, String c                          // Fall back to extracting to the tmp dir             customPath = System.getProperty("java.io.tmpdir");             File target = extract(errors, resource, prefix, suffix, file(customPath));             if( target!=null ) {                 if( load(errors, target) ) {                     return true;                 } private String map(String libName) {         return libName;     }      private File extract(ArrayList<String> errors, URL source, String prefix, String suffix, File directory) {         File target = null;         try {             FileOutputStream os = null;             InputStream is = null;             try {                 target = File.createTempFile(prefix, suffix, directory);                 is = source.openStream();                 if (is != null) {                     byte[] buffer = new byte[4096];                     os = new FileOutputStream(target);                     int read;                     while ((read = is.read(buffer)) != -1) {                         os.write(buffer, 0, read);                     }                     chmod("755", target);                 }                 target.deleteOnExit();                 return target;             } finally {                 close(os);                 close(is);             }         } catch (Throwable e) {             if( target!=null ) {                 target.delete();             }             errors.add(e.getMessage());         }         return null;     }      static private void close(Closeable file) {         if(file!=null) {             try {                 file.close();             } catch (Exception ignore) {             }         }     }      private void chmod(String permision, File path) {

Patch:
patch: @@ -9,13 +9,11 @@
  *******************************************************************************/
 package org.fusesource.hawtjni.runtime;
 
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
+import java.io.*;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Random;
 import java.util.regex.Pattern;
 
 /**
@@ -206,16 +204,19 @@ final public String getLibraryFileName() {
     private boolean exractAndLoad(ArrayList<String> errors, String version, String customPath, String resourcePath) {
         URL resource = classLoader.getResource(resourcePath);
         if( resource !=null ) {
-            
+
             String libName = name + "-" + getBitModel();
             if( version !=null) {
                 libName += "-" + version;
             }
-            
+            String []libNameParts = map(libName).split("\\.");
+            String prefix = libNameParts[0]+"-";
+            String suffix = "."+libNameParts[1];
+
             if( customPath!=null ) {
                 // Try to extract it to the custom path...
-                File target = file(customPath, map(libName));
-                if( extract(errors, resource, target) ) {
+                File target = extract(errors, resource, prefix, suffix, file(customPath));
+                if( target!=null ) {
                     if( load(errors, target) ) {
                         return true;
                     }
@@ -224,8 +225,8 @@ private boolean exractAndLoad(ArrayList<String> errors, String version, String c
             
             // Fall back to extracting to the tmp dir
             customPath = System.getProperty("java.io.tmpdir");
-            File target = file(customPath, map(libName));
-            if( extract(errors, resource, target) ) {
+            File target = extract(errors, resource, prefix, suffix, file(customPath));
+            if( target!=null ) {
                 if( load(errors, target) ) {
                     return true;
                 }
@@ -259,67 +260,45 @@ private String map(String libName) {
         return libName;
     }
 
-    private boolean extract(ArrayList<String> errors, URL source, File target) {
-        FileOutputStream os = null;
-        InputStream is = null;
-        boolean extracting = false;
+    private File extract(ArrayList<String> errors, URL source, String prefix, String suffix, File directory) {
+        File target = null;
         try {
-            if (!target.exists() || isStale(source, target) ) {
+            FileOutputStream os = null;
+            InputStream is = null;
+            try {
+                target = File.createTempFile(prefix, suffix, directory);
                 is = source.openStream();
                 if (is != null) {
                     byte[] buffer = new byte[4096];
                     os = new FileOutputStream(target);
-                    extracting = true;
                     int read;
                     while ((read = is.read(buffer)) != -1) {
                         os.write(buffer, 0, read);
                     }
-                    os.close();
-                    is.close();
                     chmod("755", target);
                 }
+                target.deleteOnExit();
+                return target;
+            } finally {
+                close(os);
+                close(is);
             }
         } catch (Throwable e) {
-            try {
-                if (os != null)
-                    os.close();
-            } catch (IOException e1) {
-            }
-            try {
-                if (is != null)
-                    is.close();
-            } catch (IOException e1) {
-            }
-            if (extracting && target.exists())
+            if( target!=null ) {
                 target.delete();
+            }
             errors.add(e.getMessage());
-            return false;
         }
-        return true;
+        return null;
     }
 
-    private boolean isStale(URL source, File target) {
-        
-        if( source.getProtocol().equals("jar") ) {
-            // unwrap the jar protocol...
+    static private void close(Closeable file) {
+        if(file!=null) {
             try {
-                String parts[] = source.getFile().split(Pattern.quote("!"));
-                source = new URL(parts[0]);
-            } catch (MalformedURLException e) {
-                return false;
-            }
-        }
-        
-        File sourceFile=null;
-        if( source.getProtocol().equals("file") ) {
-            sourceFile = new File(source.getFile());
-        }
-        if( sourceFile!=null && sourceFile.exists() ) {
-            if( sourceFile.lastModified() > target.lastModified() ) {
-                return true;
+                file.close();
+            } catch (Exception ignore) {
             }
         }
-        return false;
     }
 
     private void chmod(String permision, File path) {
path: hawtjni-runtime/src/main/java/org/fusesource/hawtjni/runtime/Library.java