diff --git a/CHANGES.txt b/CHANGES.txt
index 89aaea0249..b1ab34a7e8 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
3.0.26:
+ * CVE-2015-0886 Security vulnerability in jbcrypt is addressed (CASSANDRA-9384)
* Avoid useless SSTable reads during single partition queries (CASSANDRA-16944)
* Debian init respects CASSANDRA_HEAPDUMP_DIR (CASSANDRA-13843)
* Catch UnsatisfiedLinkError in WindowsTimer (CASSANDRA-16085)
diff --git a/NEWS.txt b/NEWS.txt
index 889943bfe0..d0b9ae6a9a 100644
--- a/NEWS.txt
+++ b/NEWS.txt
@@ -42,6 +42,16 @@ restore snapshots created with the previous major version using the
'sstableloader' tool. You can upgrade the file format of your snapshots
using the provided 'sstableupgrade' tool.
+3.0.26
+======
+
+Upgrading
+---------
+ - Before you upgrade, if you are using `cassandra.auth_bcrypt_gensalt_log2_rounds` property,
+ confirm it is set to value lower than 31 otherwise Cassandra will fail to start. See CASSANDRA-9384
+ for further details. You also need to regenerate passwords for users for who the password
+ was created while the above property was set to be more than 30 otherwise they will not be able to log in.
+
3.0.25
======
diff --git a/build.xml b/build.xml
index 3e77a957ec..c30d5963ca 100644
--- a/build.xml
+++ b/build.xml
@@ -382,7 +382,7 @@
-
+
diff --git a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java
index aa98b7e7cf..897e9eb278 100644
--- a/src/java/org/apache/cassandra/auth/CassandraRoleManager.java
+++ b/src/java/org/apache/cassandra/auth/CassandraRoleManager.java
@@ -21,6 +21,7 @@ import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.*;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
@@ -118,15 +119,16 @@ public class CassandraRoleManager implements IRoleManager
};
// 2 ** GENSALT_LOG2_ROUNDS rounds of hashing will be performed.
- private static final String GENSALT_LOG2_ROUNDS_PROPERTY = Config.PROPERTY_PREFIX + "auth_bcrypt_gensalt_log2_rounds";
+ @VisibleForTesting
+ public static final String GENSALT_LOG2_ROUNDS_PROPERTY = Config.PROPERTY_PREFIX + "auth_bcrypt_gensalt_log2_rounds";
private static final int GENSALT_LOG2_ROUNDS = getGensaltLogRounds();
static int getGensaltLogRounds()
{
int rounds = Integer.getInteger(GENSALT_LOG2_ROUNDS_PROPERTY, 10);
- if (rounds < 4 || rounds > 31)
+ if (rounds < 4 || rounds > 30)
throw new ConfigurationException(String.format("Bad value for system property -D%s." +
- "Please use a value between 4 and 31 inclusively",
+ "Please use a value between 4 and 30 inclusively",
GENSALT_LOG2_ROUNDS_PROPERTY));
return rounds;
}
diff --git a/test/unit/org/apache/cassandra/auth/PasswordAuthenticatorTest.java b/test/unit/org/apache/cassandra/auth/PasswordAuthenticatorTest.java
index 02850499cf..3909642b23 100644
--- a/test/unit/org/apache/cassandra/auth/PasswordAuthenticatorTest.java
+++ b/test/unit/org/apache/cassandra/auth/PasswordAuthenticatorTest.java
@@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets;
import com.google.common.collect.Iterables;
import org.junit.AfterClass;
+import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -31,6 +32,7 @@ import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.exceptions.AuthenticationException;
+import org.apache.cassandra.exceptions.ConfigurationException;
import org.apache.cassandra.schema.KeyspaceParams;
import static org.apache.cassandra.auth.CassandraRoleManager.*;
@@ -46,17 +48,17 @@ public class PasswordAuthenticatorTest extends CQLTester
private static PasswordAuthenticator authenticator = new PasswordAuthenticator();
@Test
- public void testCheckpw() throws Exception
+ public void testCheckpw()
{
// Valid and correct
assertTrue(checkpw(DEFAULT_SUPERUSER_PASSWORD, hashpw(DEFAULT_SUPERUSER_PASSWORD, gensalt(getGensaltLogRounds()))));
assertTrue(checkpw(DEFAULT_SUPERUSER_PASSWORD, hashpw(DEFAULT_SUPERUSER_PASSWORD, gensalt(4))));
- assertTrue(checkpw(DEFAULT_SUPERUSER_PASSWORD, hashpw(DEFAULT_SUPERUSER_PASSWORD, gensalt(31))));
+ assertTrue(checkpw(DEFAULT_SUPERUSER_PASSWORD, hashpw(DEFAULT_SUPERUSER_PASSWORD, gensalt(12))));
// Valid but incorrect hashes
assertFalse(checkpw(DEFAULT_SUPERUSER_PASSWORD, hashpw("incorrect0", gensalt(4))));
assertFalse(checkpw(DEFAULT_SUPERUSER_PASSWORD, hashpw("incorrect1", gensalt(10))));
- assertFalse(checkpw(DEFAULT_SUPERUSER_PASSWORD, hashpw("incorrect2", gensalt(31))));
+ assertFalse(checkpw(DEFAULT_SUPERUSER_PASSWORD, hashpw("incorrect2", gensalt(12))));
// Invalid hash values, the jBCrypt library implementation
// throws an exception which we catch and treat as a failure
@@ -78,6 +80,37 @@ public class PasswordAuthenticatorTest extends CQLTester
assertFalse(checkpw(DEFAULT_SUPERUSER_PASSWORD, "$2a$6$abcdefghijklmnopqrstuvABCDEFGHIJKLMNOPQRSTUVWXYZ01234"));
}
+ @Test(expected = ConfigurationException.class)
+ public void testInvalidUpperBoundHashingRoundsValue()
+ {
+ executeSaltRoundsPropertyTest(31);
+ }
+
+ @Test(expected = ConfigurationException.class)
+ public void testInvalidLowerBoundHashingRoundsValue()
+ {
+ executeSaltRoundsPropertyTest(3);
+ }
+
+ private void executeSaltRoundsPropertyTest(Integer rounds)
+ {
+ String oldProperty = System.getProperty(GENSALT_LOG2_ROUNDS_PROPERTY);
+ try
+ {
+ System.setProperty(GENSALT_LOG2_ROUNDS_PROPERTY, rounds.toString());
+ getGensaltLogRounds();
+ Assert.fail("Property " + GENSALT_LOG2_ROUNDS_PROPERTY + " must be in interval [4,30]");
+ }
+ finally
+ {
+ if (oldProperty != null)
+ System.setProperty(GENSALT_LOG2_ROUNDS_PROPERTY, oldProperty);
+ else
+ System.clearProperty(GENSALT_LOG2_ROUNDS_PROPERTY);
+ }
+ }
+
+
@Test(expected = AuthenticationException.class)
public void testEmptyUsername()
{