-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CASSANDRA-11452: Stronger resistence to hash collision attacks
Thanks to further discussions with the Cassandra and TinyLFU folks, this is a fairly strong guard against the collision attack. This also has very low overhead and impact to existing hit rates. The previous commit was fairly weak in comparison. We concluded that some randomness was desirable in either the admission or eviction policy. A few of the better options were simulated and this is my favorite balance. It might change when Roy presents his feedback from pondering it on his long flight home. The attack requires that the victim be pinned to an artifically high frequency due to a hash collision, so that no entries are admitted. When a warm candidate is rejected by the frequency filter, we give it a 1% chance of being accepted anyway. This kicks out the attacker's victim and protects well against her having multiple collisions in her arsenal. In small and large traces it is rarely triggered due to normal usage, and does not admit too frequency to hurt the hit rate. The other attack vector is the sketch's hash functions. This is already protected with a random seed. We now protect against the weak 32-bit hash codes used by Java.
- Loading branch information
Showing
11 changed files
with
118 additions
and
213 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ | |
import java.util.concurrent.ThreadLocalRandom; | ||
|
||
import javax.annotation.Nonnegative; | ||
import javax.annotation.Nonnull; | ||
import javax.annotation.concurrent.NotThreadSafe; | ||
|
||
/** | ||
|
@@ -28,7 +29,7 @@ | |
* @author [email protected] (Ben Manes) | ||
*/ | ||
@NotThreadSafe | ||
final class FrequencySketch { | ||
final class FrequencySketch<E> { | ||
|
||
/* | ||
* This class maintains a 4-bit CountMinSketch [1] with periodic aging to provide the popularity | ||
|
@@ -114,16 +115,16 @@ public boolean isNotInitialized() { | |
/** | ||
* Returns the estimated number of occurrences of an element, up to the maximum (15). | ||
* | ||
* @param hashCode the hash code of the element to count occurrences of | ||
* @param e the element to count occurrences of | ||
* @return the estimated number of occurrences of the element; possibly zero but never negative | ||
*/ | ||
@Nonnegative | ||
public int frequency(int hashCode) { | ||
public int frequency(@Nonnull E e) { | ||
if (isNotInitialized()) { | ||
return 0; | ||
} | ||
|
||
int hash = spread(hashCode); | ||
int hash = spread(e.hashCode()); | ||
int start = (hash & 3) << 2; | ||
int frequency = Integer.MAX_VALUE; | ||
for (int i = 0; i < 4; i++) { | ||
|
@@ -139,14 +140,14 @@ public int frequency(int hashCode) { | |
* of all elements will be periodically down sampled when the observed events exceeds a threshold. | ||
* This process provides a frequency aging to allow expired long term entries to fade away. | ||
* | ||
* @param hashCode the hash code of the element to count occurrences of | ||
* @param e the element to add | ||
*/ | ||
public void increment(int hashCode) { | ||
public void increment(@Nonnull E e) { | ||
if (isNotInitialized()) { | ||
return; | ||
} | ||
|
||
int hash = spread(hashCode); | ||
int hash = spread(e.hashCode()); | ||
int start = (hash & 3) << 2; | ||
|
||
// Loop unrolling improves throughput by 5m ops/s | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,21 +30,21 @@ | |
* @author [email protected] (Ben Manes) | ||
*/ | ||
public final class FrequencySketchTest { | ||
final int item = ThreadLocalRandom.current().nextInt(); | ||
final Integer item = ThreadLocalRandom.current().nextInt(); | ||
|
||
@Test | ||
public void construc() { | ||
FrequencySketch sketch = new FrequencySketch(); | ||
FrequencySketch<Integer> sketch = new FrequencySketch<>(); | ||
assertThat(sketch.table, is(nullValue())); | ||
} | ||
|
||
@Test(dataProvider = "sketch", expectedExceptions = IllegalArgumentException.class) | ||
public void ensureCapacity_negative(FrequencySketch sketch) { | ||
public void ensureCapacity_negative(FrequencySketch<Integer> sketch) { | ||
sketch.ensureCapacity(-1); | ||
} | ||
|
||
@Test(dataProvider = "sketch") | ||
public void ensureCapacity_smaller(FrequencySketch sketch) { | ||
public void ensureCapacity_smaller(FrequencySketch<Integer> sketch) { | ||
int size = sketch.table.length; | ||
sketch.ensureCapacity(size / 2); | ||
assertThat(sketch.table.length, is(size)); | ||
|
@@ -53,7 +53,7 @@ public void ensureCapacity_smaller(FrequencySketch sketch) { | |
} | ||
|
||
@Test(dataProvider = "sketch") | ||
public void ensureCapacity_larger(FrequencySketch sketch) { | ||
public void ensureCapacity_larger(FrequencySketch<Integer> sketch) { | ||
int size = sketch.table.length; | ||
sketch.ensureCapacity(size * 2); | ||
assertThat(sketch.table.length, is(size * 2)); | ||
|
@@ -62,21 +62,21 @@ public void ensureCapacity_larger(FrequencySketch sketch) { | |
} | ||
|
||
@Test(dataProvider = "sketch") | ||
public void increment_once(FrequencySketch sketch) { | ||
public void increment_once(FrequencySketch<Integer> sketch) { | ||
sketch.increment(item); | ||
assertThat(sketch.frequency(item), is(1)); | ||
} | ||
|
||
@Test(dataProvider = "sketch") | ||
public void increment_max(FrequencySketch sketch) { | ||
public void increment_max(FrequencySketch<Integer> sketch) { | ||
for (int i = 0; i < 20; i++) { | ||
sketch.increment(item); | ||
} | ||
assertThat(sketch.frequency(item), is(15)); | ||
} | ||
|
||
@Test(dataProvider = "sketch") | ||
public void increment_distinct(FrequencySketch sketch) { | ||
public void increment_distinct(FrequencySketch<Integer> sketch) { | ||
sketch.increment(item); | ||
sketch.increment(item + 1); | ||
assertThat(sketch.frequency(item), is(1)); | ||
|
@@ -87,7 +87,7 @@ public void increment_distinct(FrequencySketch sketch) { | |
@Test | ||
public void reset() { | ||
boolean reset = false; | ||
FrequencySketch sketch = new FrequencySketch(); | ||
FrequencySketch<Integer> sketch = new FrequencySketch<>(); | ||
sketch.ensureCapacity(64); | ||
|
||
for (int i = 1; i < 20 * sketch.table.length; i++) { | ||
|
@@ -103,20 +103,20 @@ public void reset() { | |
|
||
@Test | ||
public void heavyHitters() { | ||
FrequencySketch sketch = makeSketch(512); | ||
FrequencySketch<Double> sketch = makeSketch(512); | ||
for (int i = 100; i < 100_000; i++) { | ||
sketch.increment(Double.hashCode(i)); | ||
sketch.increment((double) i); | ||
} | ||
for (int i = 0; i < 10; i += 2) { | ||
for (int j = 0; j < i; j++) { | ||
sketch.increment(Double.hashCode(i)); | ||
sketch.increment((double) i); | ||
} | ||
} | ||
|
||
// A perfect popularity count yields an array [0, 0, 2, 0, 4, 0, 6, 0, 8, 0] | ||
int[] popularity = new int[10]; | ||
for (int i = 0; i < 10; i++) { | ||
popularity[i] = sketch.frequency(Double.hashCode(i)); | ||
popularity[i] = sketch.frequency((double) i); | ||
} | ||
for (int i = 0; i < popularity.length; i++) { | ||
if ((i == 0) || (i == 1) || (i == 3) || (i == 5) || (i == 7) || (i == 9)) { | ||
|
@@ -136,8 +136,8 @@ public Object[][] providesSketch() { | |
return new Object[][] {{ makeSketch(512) }}; | ||
} | ||
|
||
private static FrequencySketch makeSketch(long maximumSize) { | ||
FrequencySketch sketch = new FrequencySketch(); | ||
private static <E> FrequencySketch<E> makeSketch(long maximumSize) { | ||
FrequencySketch<E> sketch = new FrequencySketch<>(); | ||
sketch.ensureCapacity(maximumSize); | ||
ensureRandomSeed(sketch); | ||
return sketch; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.