Repository: microsoft/mssql-jdbc
Pull request #1855 (2022-06-27 19:12:59)

Fixed race condition with addressList
Fix for https://github.com/microsoft/mssql-jdbc/issues/1852

tkyc: /azp run CI-MacOS-clone
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>

Patch:
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
index c5a71666..5fbc526b 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java
@@ -2705,13 +2705,15 @@ final class SocketFinder {
      */
     private InetSocketAddress getInetAddressByIPPreference(String hostName,
             int portNumber) throws IOException, SQLServerException {
-        InetSocketAddress addr = InetSocketAddress.createUnresolved(hostName, portNumber);
-        for (int i = 0; i < addressList.size(); i++) {
-            addr = new InetSocketAddress(addressList.get(i), portNumber);
-            if (!addr.isUnresolved())
-                return addr;
+        synchronized (addressList) {
+            InetSocketAddress addr = InetSocketAddress.createUnresolved(hostName, portNumber);
+            for (int i = 0; i < addressList.size(); i++) {
+                addr = new InetSocketAddress(addressList.get(i), portNumber);
+                if (!addr.isUnresolved())
+                    return addr;
+            }
+            return addr;
         }
-        return addr;
     }
 
     /**
@@ -2794,17 +2796,19 @@ final class SocketFinder {
      *        Boolean switch for IPv6 first
      */
     private void fillAddressList(InetAddress[] addresses, boolean ipv6first) {
-        addressList.clear();
-        if (ipv6first) {
-            for (InetAddress addr : addresses) {
-                if (addr instanceof Inet6Address) {
-                    addressList.add(addr);
+        synchronized (addressList) {
+            addressList.clear();
+            if (ipv6first) {
+                for (InetAddress addr : addresses) {
+                    if (addr instanceof Inet6Address) {
+                        addressList.add(addr);
+                    }
                 }
-            }
-        } else {
-            for (InetAddress addr : addresses) {
-                if (addr instanceof Inet4Address) {
-                    addressList.add(addr);
+            } else {
+                for (InetAddress addr : addresses) {
+                    if (addr instanceof Inet4Address) {
+                        addressList.add(addr);
+                    }
                 }
             }
         }
diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java b/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java
index 0b5de8d8..2ed97efb 100644
--- a/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java
+++ b/src/main/java/com/microsoft/sqlserver/jdbc/SecureStringUtil.java
@@ -31,9 +31,6 @@ final class SecureStringUtil {
     /* authentication tag length in bits */
     static final int TAG_LENGTH = 16;
 
-    /* initialization vector */
-    byte[] iv;
-
     /** secret key for encryption/decryption */
     SecretKeySpec secretKey;
 
@@ -68,7 +65,6 @@ final class SecureStringUtil {
      *         if error
      */
     private SecureStringUtil() throws SQLServerException {
-        iv = new byte[IV_LENGTH];
         try {
             // generate key */
             KeyGenerator keygen = KeyGenerator.getInstance(KEYGEN_ALGORITHEM);
@@ -99,6 +95,8 @@ final class SecureStringUtil {
     byte[] getEncryptedBytes(char[] chars) throws SQLServerException {
         if (chars == null)
             return null;
+
+        byte[] iv = new byte[IV_LENGTH];
         SecureRandom random = new SecureRandom();
         random.nextBytes(iv);
         GCMParameterSpec ivParamSpec = new GCMParameterSpec(TAG_LENGTH * 8, iv);
@@ -107,7 +105,10 @@ final class SecureStringUtil {
             encryptCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParamSpec);
 
             byte[] cipherText = encryptCipher.doFinal(Util.charsToBytes(chars));
-            return cipherText;
+            byte[] bytes = new byte[iv.length + cipherText.length];
+            System.arraycopy(iv, 0, bytes, 0, iv.length);
+            System.arraycopy(cipherText, 0, bytes, iv.length, cipherText.length);
+            return bytes;
         } catch (Exception e) {
             MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_EncryptionFailed"));
             Object[] msgArgs = {e.getMessage()};
@@ -127,13 +128,17 @@ final class SecureStringUtil {
     char[] getDecryptedChars(byte[] bytes) throws SQLServerException {
         if (bytes == null)
             return null;
+
+        byte[] iv = new byte[IV_LENGTH];
+        System.arraycopy(bytes, 0, iv, 0, IV_LENGTH);
+
         GCMParameterSpec ivParamSpec = new GCMParameterSpec(TAG_LENGTH * 8, iv);
 
         byte[] plainText = null;
         try {
             decryptCipher.init(Cipher.DECRYPT_MODE, secretKey, ivParamSpec);
 
-            plainText = decryptCipher.doFinal(bytes);
+            plainText = decryptCipher.doFinal(bytes, IV_LENGTH, bytes.length - IV_LENGTH);
             return Util.bytesToChars(plainText);
         } catch (Exception e) {
             MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_DecryptionFailed"));


Test patch:
diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java
index bddd9a26..835bab8e 100644
--- a/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java
+++ b/src/test/java/com/microsoft/sqlserver/jdbc/UtilTest.java
@@ -66,6 +66,8 @@ public class UtilTest {
     }
 
     private static String testString = "A ß € 嗨 𝄞 🙂ăѣ𝔠ծềſģȟᎥ𝒋ǩľḿꞑȯ𝘱𝑞𝗋𝘴ȶ𝞄𝜈ψ𝒙𝘆𝚣1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~𝘈Ḇ𝖢𝕯٤ḞԍНǏ𝙅ƘԸⲘ𝙉০Ρ𝗤Ɍ𝓢ȚЦ𝒱Ѡ𝓧ƳȤѧᖯć𝗱ễ𝑓𝙜Ⴙ𝞲𝑗𝒌ļṃŉо𝞎𝒒ᵲꜱ𝙩ừ𝗏ŵ𝒙𝒚ź1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~АḂⲤ𝗗𝖤𝗙ꞠꓧȊ𝐉𝜥ꓡ𝑀𝑵Ǭ𝙿𝑄Ŗ𝑆𝒯𝖴𝘝𝘞ꓫŸ𝜡ả𝘢ƀ𝖼ḋếᵮℊ𝙝Ꭵ𝕛кιṃդⱺ𝓅𝘲𝕣𝖘ŧ𝑢ṽẉ𝘅ყž1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~Ѧ𝙱ƇᗞΣℱԍҤ١𝔍К𝓛𝓜ƝȎ𝚸𝑄Ṛ𝓢ṮṺƲᏔꓫ𝚈𝚭𝜶Ꮟçძ𝑒𝖿𝗀ḧ𝗂𝐣ҝɭḿ𝕟𝐨𝝔𝕢ṛ𝓼тú𝔳ẃ⤬𝝲𝗓1234567890!@#$%^&*()-_=+[{]};:'\",<.>/?~𝖠Β𝒞𝘋𝙴𝓕ĢȞỈ𝕵ꓗʟ𝙼ℕ০𝚸𝗤ՀꓢṰǓⅤ𝔚Ⲭ𝑌𝙕𝘢𝕤";
+    private static String testString2 = "ssdfsdflkjh9u0345)*&)(*&%$";
+    private static String testString3 = "ss345(*&^%oujdf.';lk2345(*&()*$#~!`1\\]wer><.,/?dfsdflkjh9u0345)*&)(*&%$";
 
     @Test
     public void testArrayConversions() {
@@ -79,9 +81,17 @@ public class UtilTest {
 
     @Test
     public void testSecureStringUtil() throws SQLException {
+        // Encrypt/decrypt multiple values in overlapping orders
         byte[] bytes = SecureStringUtil.getInstance().getEncryptedBytes(testString.toCharArray());
+        byte[] bytes2 = SecureStringUtil.getInstance().getEncryptedBytes(testString2.toCharArray());
         String end = String.valueOf(SecureStringUtil.getInstance().getDecryptedChars(bytes));
+        byte[] bytes3 = SecureStringUtil.getInstance().getEncryptedBytes(testString3.toCharArray());
+        String end3 = String.valueOf(SecureStringUtil.getInstance().getDecryptedChars(bytes3));
+        String end2 = String.valueOf(SecureStringUtil.getInstance().getDecryptedChars(bytes2));
+
         assertEquals(testString, end);
+        assertEquals(testString2, end2);
+        assertEquals(testString3, end3);
     }
 
     private void writeAndReadLong(long valueToTest) {