Sat Apr 30 06:32:41 2016  Vladimir Makarov  <vmakarov@redhat.com>

	* include/ruby/st.h (st_hash_type): New field strong_hash.
	(struct st_table): New fields curr_hash and inside_rebuild_p.
	* st.c (do_hash): Use curr_hash.
	(make_tab_empty): Set up curr_hash.
	(st_init_table_with_size): Initialize inside_rebuld_p.
	(rebuild_table): Set up curr_hash and inside_rebuild_p.
	(reset_entry_hashes): New.
	(HIT_THRESHOULD_FOR_STRONG_HASH): New.
	(find_table_bin_ptr_and_reserve): Switch to the strong hash when
	it is necessary.
	* hash.c (str_seed): New.
	(any_hash): Rename to any_hash_general.  Add parameter
	strong_p.  Use it to choose rb_str_hash or st_hash.
	(any_hash_weak, rb_any_hash_weak, any_hash): New.
	(objhash): Add rb_any_hash_weak.

diff --git a/hash.c b/hash.c
index d85ebec..f2f0e97 100644
--- a/hash.c
+++ b/hash.c
@@ -145,8 +145,14 @@ rb_hash(VALUE obj)
 
 long rb_objid_hash(st_index_t index);
 
+#if SIZEOF_INT == SIZEOF_VOIDP
+static const st_index_t str_seed = 0xfa835867;
+#else
+static const st_index_t str_seed = 0xc42b5e2e6480b23bULL;
+#endif
+
 static inline st_index_t
-any_hash(VALUE a, st_index_t (*other_func)(VALUE))
+any_hash_general(VALUE a, int strong_p, st_index_t (*other_func)(VALUE))
 {
     VALUE hval;
     st_index_t hnum;
@@ -163,7 +169,9 @@ any_hash(VALUE a, st_index_t (*other_func)(VALUE))
 	hnum = rb_objid_hash((st_index_t)a);
     }
     else if (BUILTIN_TYPE(a) == T_STRING) {
-	hnum = rb_str_hash(a);
+	hnum = (strong_p
+		? rb_str_hash(a)
+		: st_hash(RSTRING_PTR(a), RSTRING_LEN(a), str_seed));
     }
     else if (BUILTIN_TYPE(a) == T_SYMBOL) {
 	hnum = RSYMBOL(a)->hashval;
@@ -200,9 +208,23 @@ obj_any_hash(VALUE obj)
     return FIX2LONG(obj);
 }
 
+static inline st_index_t
+any_hash_weak(VALUE a, st_index_t (*other_func)(VALUE)) {
+    return any_hash_general(a, FALSE, other_func);
+}
+
 static st_index_t
-rb_any_hash(VALUE a)
-{
+rb_any_hash_weak(VALUE a) {
+    return any_hash_weak(a, obj_any_hash);
+}
+
+static inline st_index_t
+any_hash(VALUE a, st_index_t (*other_func)(VALUE)) {
+    return any_hash_general(a, TRUE, other_func);
+}
+
+static st_index_t
+rb_any_hash(VALUE a) {
     return any_hash(a, obj_any_hash);
 }
 
@@ -265,6 +287,7 @@ rb_hash_iter_lev(VALUE h)
 
 static const struct st_hash_type objhash = {
     rb_any_cmp,
+    rb_any_hash_weak,
     rb_any_hash,
 };
 
diff --git a/include/ruby/st.h b/include/ruby/st.h
index e6d24f0..69644d9 100644
--- a/include/ruby/st.h
+++ b/include/ruby/st.h
@@ -61,6 +61,11 @@ typedef char st_check_for_sizeof_st_index_t[SIZEOF_VOIDP == (int)sizeof(st_index
 struct st_hash_type {
     int (*compare)(ANYARGS /*st_data_t, st_data_t*/); /* st_compare_func* */
     st_index_t (*hash)(ANYARGS /*st_data_t*/);        /* st_hash_func* */
+    /* The following is an optional func for stronger hash.  When we
+       have many different keys with the same hash we can switch to
+       use it to prevent a denial attack with usage of hash table
+       collisions. */
+    st_index_t (*strong_hash)(ANYARGS /*st_data_t*/);
 };
 
 #if defined(HAVE_BUILTIN___BUILTIN_CHOOSE_EXPR) && defined(HAVE_BUILTIN___BUILTIN_TYPES_COMPATIBLE_P)
@@ -77,8 +82,12 @@ struct st_table_entry; /* defined in st.c */
 struct st_table {
     /* Cached features of the table -- see st.c for more details.  */
     unsigned char entry_power, bin_power, size_ind;
+    /* True when we are rebuilding the table.  */
+    unsigned char inside_rebuild_p;
     /* How many times the table was rebuilt.  */
     unsigned int rebuilds_num;
+    /* Currently used hash function.  */
+    st_index_t (*curr_hash)(ANYARGS /*st_data_t*/);
     const struct st_hash_type *type;
     /* Number of entries currently in the table.  */
     st_index_t num_entries;
diff --git a/st.c b/st.c
index 1265bc8..65811fc 100644
--- a/st.c
+++ b/st.c
@@ -304,7 +304,7 @@ struct st_features features[] = {
 /* Return hash value of KEY for table TAB.  */
 static inline st_hash_t
 do_hash(st_data_t key, st_table *tab) {
-    st_index_t h = (st_index_t)(tab->type->hash)(key);
+    st_index_t h = (st_index_t)(tab->curr_hash)(key);
 #if SIZEOF_INT == SIZEOF_VOIDP
     st_hash_t hash = h;
 #else
@@ -455,6 +455,7 @@ initialize_bins(st_table *tab) {
 static void
 make_tab_empty(st_table *tab)
 {
+    tab->curr_hash = tab->type->hash;
     tab->num_entries = 0;
     tab->rebuilds_num = 0;
     tab->entries_start = tab->entries_bound = 0;
@@ -566,6 +567,7 @@ st_init_table_with_size(const struct st_hash_type *type, st_index_t size) {
     tab->entry_power = n;
     tab->bin_power = features[n].bin_power;
     tab->size_ind = features[n].size_ind;
+    tab->inside_rebuild_p = FALSE;
     if (n <= MAX_POWER2_FOR_TABLES_WITHOUT_BINS)
         tab->bins = NULL;
     else
@@ -723,6 +725,7 @@ rebuild_table(st_table *tab) {
     }
     bound = tab->entries_bound;
     entries = tab->entries;
+    tab->inside_rebuild_p = TRUE;
     if ((2 * tab->num_entries <= get_allocated_entries(tab)
 	 && REBUILD_THRESHOLD * tab->num_entries > get_allocated_entries(tab))
 	|| (tab->entry_power > MINIMAL_POWER2
@@ -737,6 +740,8 @@ rebuild_table(st_table *tab) {
     else {
         new_tab = st_init_table_with_size(tab->type,
 					  2 * tab->num_entries - 1);
+	st_assert(new_tab->curr_hash == new_tab->type->hash);
+ 	new_tab->curr_hash = tab->curr_hash;
 	new_entries = new_tab->entries;
     }
     ni = 0;
@@ -775,6 +780,7 @@ rebuild_table(st_table *tab) {
     tab->entries_start = 0;
     tab->entries_bound = tab->num_entries;
     tab->rebuilds_num++;
+    tab->inside_rebuild_p = FALSE;
 #ifdef ST_DEBUG
     st_check(tab);
 #endif
@@ -937,6 +943,28 @@ find_table_bin_ind_direct(st_table *tab, st_hash_t hash_value, st_data_t key) {
     }
 }
 
+/* Recalculate hashes of entries in table TAB.  */
+static void
+reset_entry_hashes (st_table *tab)
+{
+    st_index_t i, bound;
+    st_table_entry *entries, *curr_entry_ptr;
+    
+    bound = tab->entries_bound;
+    entries = tab->entries;
+    for (i = tab->entries_start; i < bound; i++) {
+	curr_entry_ptr = &entries[i];
+	if (! DELETED_ENTRY_P(curr_entry_ptr))
+	    curr_entry_ptr->hash = do_hash(curr_entry_ptr->key, tab);
+    }
+}
+
+/* If we have the following number of collisions with different keys
+   but with the same hash during finding a bin for new entry
+   inclusions, possibly a denial attack is going on.  Start to use a
+   stronger hash.  */
+#define HIT_THRESHOULD_FOR_STRONG_HASH 10
+
 /* Return index of table TAB bin for HASH_VALUE and KEY through
    BIN_IND and the pointed value as the function result.  Reserve the
    bin for inclusion of the corresponding entry into the table if it
@@ -958,10 +986,12 @@ find_table_bin_ptr_and_reserve(st_table *tab, st_hash_t *hash_value,
     st_index_t entry_index;
     st_index_t first_deleted_bin_ind;
     st_table_entry *entries;
+    int hit;
     
     st_assert(tab != NULL && tab->bins != NULL
 	      && tab->entries_bound <= get_allocated_entries(tab)
 	      && tab->entries_start <= tab->entries_bound);
+  repeat:
     ind = hash_bin(curr_hash_value, tab);
 #ifdef QUADRATIC_PROBE
     d = 1;
@@ -971,6 +1001,7 @@ find_table_bin_ptr_and_reserve(st_table *tab, st_hash_t *hash_value,
     FOUND_BIN;
     first_deleted_bin_ind = UNDEFINED_BIN_IND;
     entries = tab->entries;
+    hit = 0;
     for (;;) {
         entry_index = get_bin(tab->bins, get_size_ind(tab), ind);
         if (EMPTY_BIN_P(entry_index)) {
@@ -985,6 +1016,19 @@ find_table_bin_ptr_and_reserve(st_table *tab, st_hash_t *hash_value,
         } else if (! DELETED_BIN_P(entry_index)) {
             if (PTR_EQUAL(tab, &entries[entry_index - ENTRY_BASE], curr_hash_value, key))
                 break;
+	    if (curr_hash_value == entries[entry_index - ENTRY_BASE].hash) {
+ 	        hit++;
+ 		if (hit > HIT_THRESHOULD_FOR_STRONG_HASH
+ 		    && tab->curr_hash != tab->type->strong_hash
+		    && tab->type->strong_hash != NULL
+		    && ! tab->inside_rebuild_p) {
+		    tab->curr_hash = tab->type->strong_hash;
+ 		    *hash_value = curr_hash_value = do_hash(key, tab);
+ 		    reset_entry_hashes(tab);
+ 		    rebuild_table(tab);
+ 		    goto repeat;
+		}
+	    }
         } else if (first_deleted_bin_ind == UNDEFINED_BIN_IND)
             first_deleted_bin_ind = ind;
 #ifdef QUADRATIC_PROBE
