Skip to content

Commit 9d4756b

Browse files
committed
Fix #547
1 parent c0ffdc2 commit 9d4756b

File tree

3 files changed

+27
-14
lines changed

3 files changed

+27
-14
lines changed

release-notes/CREDITS-2.x

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ Alex Rebert (alpire@github)
168168
* Reported #540, suggested fix: UTF8StreamJsonParser: fix byte to int conversion for
169169
malformed escapes
170170
(2.9.10)
171+
* Reported #547: `CharsToNameCanonicalizer`: Internal error on `SymbolTable.rehash()` with high
172+
number of hash collisions
173+
(2.10.0)
171174
172175
Philippe Marschall (marschall@github)
173176
* Requested #480: `SerializableString` value can not directly render to Writer

release-notes/VERSION-2.x

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ JSON library.
3434
#533: UTF-8 BOM not accounted for in JsonLocation.getByteOffset()
3535
(contributed by Fabien R)
3636
#539: Reduce max size of recycled byte[]/char[] blocks by `TextBuffer`, `ByteArrayBuilder`
37+
#547: `CharsToNameCanonicalizer`: Internal error on `SymbolTable.rehash()` with high
38+
number of hash collisions
39+
(reported by Alex R)
3740

3841
2.9.10 (not yet released)
3942

src/main/java/com/fasterxml/jackson/core/sym/CharsToNameCanonicalizer.java

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,12 @@ public final class CharsToNameCanonicalizer
7878
/**
7979
* Also: to thwart attacks based on hash collisions (which may or may not
8080
* be cheap to calculate), we will need to detect "too long"
81-
* collision chains. Let's start with static value of 255 entries
81+
* collision chains. Let's start with static value of 100 entries
8282
* for the longest legal chain.
8383
*<p>
8484
* Note: longest chain we have been able to produce without malicious
8585
* intent has been 38 (with "com.fasterxml.jackson.core.main.TestWithTonsaSymbols");
8686
* our setting should be reasonable here.
87-
*<p>
88-
* Also note that value was lowered from 255 (2.3 and earlier) to 100 for 2.4
8987
*
9088
* @since 2.1
9189
*/
@@ -481,7 +479,7 @@ private String _addSymbol(char[] buffer, int start, int len, int h, int index)
481479
_hashShared = false;
482480
} else if (_size >= _sizeThreshold) { // Need to expand?
483481
rehash();
484-
// Need to recalc hash; rare occurence (index mask has been
482+
// Need to recalc hash; rare occurrence (index mask has been
485483
// recalculated as part of rehash)
486484
index = _hashToIndex(calcHash(buffer, start, len));
487485
}
@@ -501,7 +499,7 @@ private String _addSymbol(char[] buffer, int start, int len, int h, int index)
501499
if (collLen > MAX_COLL_CHAIN_LENGTH) {
502500
// 23-May-2014, tatu: Instead of throwing an exception right away,
503501
// let's handle in bit smarter way.
504-
_handleSpillOverflow(bix, newB);
502+
_handleSpillOverflow(bix, newB, index);
505503
} else {
506504
_buckets[bix] = newB;
507505
_longestCollisionList = Math.max(collLen, _longestCollisionList);
@@ -510,27 +508,36 @@ private String _addSymbol(char[] buffer, int start, int len, int h, int index)
510508
return newSymbol;
511509
}
512510

513-
private void _handleSpillOverflow(int bindex, Bucket newBucket)
511+
/**
512+
* Method called when an overflow bucket has hit the maximum expected length:
513+
* this may be a case of DoS attack. Deal with it based on settings by either
514+
* clearing up bucket (to avoid indefinite expansion) or throwing exception.
515+
* Currently the first overflow for any single bucket DOES NOT throw an exception,
516+
* only second time (per symbol table instance)
517+
*/
518+
private void _handleSpillOverflow(int bucketIndex, Bucket newBucket, int mainIndex)
514519
{
515520
if (_overflows == null) {
516521
_overflows = new BitSet();
517-
_overflows.set(bindex);
522+
_overflows.set(bucketIndex);
518523
} else {
519-
if (_overflows.get(bindex)) {
520-
// Has happened once already, so not a coincident...
524+
if (_overflows.get(bucketIndex)) {
525+
// Has happened once already for this bucket index, so probably not coincidental...
521526
if (JsonFactory.Feature.FAIL_ON_SYMBOL_HASH_OVERFLOW.enabledIn(_flags)) {
522527
reportTooManyCollisions(MAX_COLL_CHAIN_LENGTH);
523528
}
524-
// but even if we don't fail, we will stop canonicalizing:
529+
// but even if we don't fail, we will stop canonicalizing as safety measure
530+
// (so as not to cause problems with PermGen)
525531
_canonicalize = false;
526532
} else {
527-
_overflows.set(bindex);
533+
_overflows.set(bucketIndex);
528534
}
529535
}
536+
530537
// regardless, if we get this far, clear up the bucket, adjust size appropriately.
531-
_symbols[bindex + bindex] = newBucket.symbol;
532-
_buckets[bindex] = null;
533-
// newBucket contains new symbol; but we wil
538+
_symbols[mainIndex] = newBucket.symbol;
539+
_buckets[bucketIndex] = null;
540+
// newBucket contains new symbol; but we will
534541
_size -= (newBucket.length);
535542
// we could calculate longest; but for now just mark as invalid
536543
_longestCollisionList = -1;

0 commit comments

Comments
 (0)