Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(198)

Side by Side Diff: openssl/patches/fix_lhash_iteration.patch

Issue 57833003: Fix lhash implementation to avoid unwanted resizes during iteration. (Closed) Base URL: https://chromium.googlesource.com/chromium/deps/openssl@master
Patch Set: Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 From 84726440ecdd3ecd0c3d8571cc6a50bad769b5b8 Mon Sep 17 00:00:00 2001
2 From: David 'Digit' Turner <digit@android.com>
3 Date: Tue, 29 Oct 2013 18:40:45 +0100
4 Subject: Ensure iterating over a lhash table prevents unwanted resizes.
5
6 This patch modifies the implementation of lhash tables to ensure
7 that deleting or inserting items during a lh_doall or lh_doall_arg
8 call will not unexpectedly resize the buckets array, leading to
9 undefined behaviour.
10
11 This is done by adding a new field ('iteration_state') to the
12 lhash_st structure. Note that the field is placed at the end
13 of the structure to preserve the OpenSSL ABI [1]
14
15 This also removes the 'evil' changes to the 'down_load' field,
16 which were performed to prevent this. Unfortunately, this didn't
17 work due to an incorrect load-comparison computation is
18 lh_delete() [2]
19
20 [1] Since all _LHASH objects are heap-allocated through lh_new(),
21 client code that accesses fields directly will not be
22 impacted if their offsets do not change.
23
24 [2] The original code used the following expression to determine
25 when to contract the table during a lh_delete() operation:
26
27 down_load >= (num_items * LOAD_MULT / num_nodes)
28
29 Unfortunately, due to truncation towards zero, this could
30 trigger even when down_load was set to 0 in certain cases.
31 See the comments for the LH_SHOULD_CONTRACT() macro for
32 more explanations.
33
34 Change-Id: I9fa5b84c5d787d41fd520b63f7e641cf22fbb5b6
35 ---
36 crypto/conf/conf_api.c | 3 --
37 crypto/lhash/lhash.c | 134 +++++++++++++++++++++++++++++++++++++++++++++--
38 crypto/lhash/lhash.h | 3 +-
39 crypto/objects/o_names.c | 6 ---
40 crypto/objects/obj_dat.c | 1 -
41 include/openssl/lhash.h | 1 +
42 ssl/ssl_sess.c | 3 --
43 7 files changed, 134 insertions(+), 17 deletions(-)
44
45 diff --git a/crypto/conf/conf_api.c b/crypto/conf/conf_api.c
46 index f5fcbb9..9a37bd4 100644
47 --- a/crypto/conf/conf_api.c
48 +++ b/crypto/conf/conf_api.c
49 @@ -225,9 +225,6 @@ void _CONF_free_data(CONF *conf)
50 {
51 if (conf == NULL || conf->data == NULL) return;
52
53 - lh_CONF_VALUE_down_load(conf->data)=0; /* evil thing to make
54 - * sure the 'OPENSSL_free()' works as
55 - * expected */
56 lh_CONF_VALUE_doall_arg(conf->data,
57 LHASH_DOALL_ARG_FN(value_free_hash),
58 LHASH_OF(CONF_VALUE), conf->data);
59 diff --git a/crypto/lhash/lhash.c b/crypto/lhash/lhash.c
60 index 47f7480..6278c54 100644
61 --- a/crypto/lhash/lhash.c
62 +++ b/crypto/lhash/lhash.c
63 @@ -94,6 +94,7 @@
64 *
65 * 1.0 eay - First version
66 */
67 +#include <limits.h>
68 #include <stdio.h>
69 #include <string.h>
70 #include <stdlib.h>
71 @@ -107,6 +108,113 @@ const char lh_version[]="lhash" OPENSSL_VERSION_PTEXT;
72 #define UP_LOAD (2*LH_LOAD_MULT) /* load times 256 (default 2) */
73 #define DOWN_LOAD (LH_LOAD_MULT) /* load times 256 (default 1) */
74
75 +/* Maximum number of nodes to guarantee the load computations don't overflow */
76 +#define MAX_LOAD_ITEMS (UINT_MAX / LH_LOAD_MULT)
77 +
78 +/* The field 'iteration_state' is used to hold data to ensure that a hash
79 + * table is not resized during an 'insert' or 'delete' operation performed
80 + * within a lh_doall/lh_doall_arg call.
81 + *
82 + * Conceptually, this records two things:
83 + *
84 + * - A 'depth' count, which is incremented at the start of lh_doall*,
85 + * and decremented just before it returns.
86 + *
87 + * - A 'mutated' boolean flag, which is set in lh_insert() or lh_delete()
88 + * when the operation is performed with a non-0 depth.
89 + *
90 + * The following are helper macros to handle this state in a more explicit
91 + * way.
92 + */
93 +
94 +/* Reset the iteration state to its defaults. */
95 +#define LH_ITERATION_RESET(lh) do { \
96 + (lh)->iteration_state = 0; \
97 + } while (0)
98 +
99 +/* Returns 1 if the hash table is currently being iterated on, 0 otherwise. */
100 +#define LH_ITERATION_IS_ACTIVE(lh) ((lh)->iteration_state >= 2)
101 +
102 +/* Increment iteration depth. This should always be followed by a paired call
103 + * to LH_ITERATION_DECREMENT_DEPTH(). */
104 +#define LH_ITERATION_INCREMENT_DEPTH(lh) do { \
105 + (lh)->iteration_state += 2; \
106 + } while (0)
107 +
108 +/* Decrement iteration depth. This should always be called after a paired call
109 + * to LH_ITERATION_INCREMENT_DEPTH(). */
110 +#define LH_ITERATION_DECREMENT_DEPTH(lh) do { \
111 + (lh)->iteration_state -= 2; \
112 + } while (0)
113 +
114 +/* Return 1 if the iteration 'mutated' flag is set, 0 otherwise. */
115 +#define LH_ITERATION_IS_MUTATED(lh) (((lh)->iteration_state & 1) != 0)
116 +
117 +/* Set the iteration 'mutated' flag to 1. LH_ITERATION_RESET() to reset it. */
118 +#define LH_ITERATION_SET_MUTATED(lh) do { \
119 + (lh)->iteration_state |= 1; \
120 + } while (0)
121 +
122 +/* This macros returns 1 if the hash table should be expanded due to its curren t
123 + * load, or 0 otherwise. The exact comparison to be performed is expressed by
124 + * the mathematical expression (where '//' denotes the exact division operation ):
125 + *
126 + * (num_items // num_nodes) >= (up_load // LOAD_MULT) or
127 + * (num_items * LOAD_MULT // num_nodes) >= up_load.
128 + *
129 + * Given that the C language operator '/' implements integer division, i.e:
130 + * a // b == (a / b) + epsilon (with 0 <= epsilon < 1, for positive a & b)
131 + *
132 + * This can be rewritten as:
133 + * (num_items * LOAD_MULT / num_nodes) + epsilon >= up_load
134 + * (num_items * LOAD_MULT / num_nodes) - up_load >= - epsilon
135 + *
136 + * Let's call 'A' the left-hand side of the equation above, it is an integer
137 + * and:
138 + * - If A >= 0, the expression is true for any value of epsilon.
139 + * - If A <= -1, the expression is also true for any value of epsilon.
140 + *
141 + * In other words, this is equivalent to 'A >= 0', or:
142 + * (num_items * LOAD_MULT / num_nodes) >= up_load
143 + */
144 +#define LH_SHOULD_EXPAND(lh) \
145 + ((lh)->num_items < MAX_LOAD_ITEMS && \
146 + (((lh)->num_items*LH_LOAD_MULT/(lh)->num_nodes) >= (lh)->up_load))
147 +
148 +/* This macro returns 1 if the hash table should be contracted due to its
149 + * current load, or 0 otherwise. Abbreviated computations are:
150 + *
151 + * (num_items // num_nodes) <= (down_load // LOAD_MULT)
152 + * (num_items * LOAD_MULT // num_nodes) <= down_load
153 + * (num_items * LOAD_MULT / num_nodes) + epsilon <= down_load
154 + * (num_items * LOAD_MULT / num_nodes) - down_load <= -epsilon
155 + *
156 + * Let's call 'B' the left-hand side of the equation above:
157 + * - If B <= -1, the expression is true for any value of epsilon.
158 + * - If B >= 1, the expression is false for any value of epsilon.
159 + * - If B == 0, the expression is true for 'epsilon == 0', and false
160 + * otherwise, which is problematic.
161 + *
162 + * To work around this problem, while keeping the code simple, just change
163 + * the initial expression to use a strict inequality, i.e.:
164 + *
165 + * (num_items // num_nodes) < (down_load // LOAD_MULT)
166 + *
167 + * Which leads to:
168 + * (num_items * LOAD_MULT / num_nodes) - down_load < -epsilon
169 + *
170 + * Then:
171 + * - If 'B <= -1', the expression is true for any value of epsilon.
172 + * - If 'B' >= 0, the expression is false for any value of epsilon,
173 + *
174 + * In other words, this is equivalent to 'B < 0', or:
175 + * (num_items * LOAD_MULT / num_nodes) < down_load
176 + */
177 +#define LH_SHOULD_CONTRACT(lh) \
178 + (((lh)->num_nodes > MIN_NODES) && \
179 + ((lh)->num_items < MAX_LOAD_ITEMS && \
180 + ((lh)->num_items*LH_LOAD_MULT/(lh)->num_nodes) < (lh)->down_load))
181 +
182 static void expand(_LHASH *lh);
183 static void contract(_LHASH *lh);
184 static LHASH_NODE **getrn(_LHASH *lh, const void *data, unsigned long *rhash);
185 @@ -147,6 +255,7 @@ _LHASH *lh_new(LHASH_HASH_FN_TYPE h, LHASH_COMP_FN_TYPE c)
186 ret->num_hash_comps=0;
187
188 ret->error=0;
189 + LH_ITERATION_RESET(ret);
190 return(ret);
191 err1:
192 OPENSSL_free(ret);
193 @@ -183,7 +292,10 @@ void *lh_insert(_LHASH *lh, void *data)
194 void *ret;
195
196 lh->error=0;
197 - if (lh->up_load <= (lh->num_items*LH_LOAD_MULT/lh->num_nodes))
198 + /* Do not expand the array if the table is being iterated on. */
199 + if (LH_ITERATION_IS_ACTIVE(lh))
200 + LH_ITERATION_SET_MUTATED(lh);
201 + else if (LH_SHOULD_EXPAND(lh))
202 expand(lh);
203
204 rn=getrn(lh,data,&hash);
205 @@ -238,8 +350,10 @@ void *lh_delete(_LHASH *lh, const void *data)
206 }
207
208 lh->num_items--;
209 - if ((lh->num_nodes > MIN_NODES) &&
210 - (lh->down_load >= (lh->num_items*LH_LOAD_MULT/lh->num_nodes)))
211 + /* Do not contract the array if the table is being iterated on. */
212 + if (LH_ITERATION_IS_ACTIVE(lh))
213 + LH_ITERATION_SET_MUTATED(lh);
214 + else if (LH_SHOULD_CONTRACT(lh))
215 contract(lh);
216
217 return(ret);
218 @@ -276,6 +390,7 @@ static void doall_util_fn(_LHASH *lh, int use_arg, LHASH_DOA LL_FN_TYPE func,
219 if (lh == NULL)
220 return;
221
222 + LH_ITERATION_INCREMENT_DEPTH(lh);
223 /* reverse the order so we search from 'top to bottom'
224 * We were having memory leaks otherwise */
225 for (i=lh->num_nodes-1; i>=0; i--)
226 @@ -295,6 +410,19 @@ static void doall_util_fn(_LHASH *lh, int use_arg, LHASH_DO ALL_FN_TYPE func,
227 a=n;
228 }
229 }
230 +
231 + LH_ITERATION_DECREMENT_DEPTH(lh);
232 + if (!LH_ITERATION_IS_ACTIVE(lh) && LH_ITERATION_IS_MUTATED(lh))
233 + {
234 + LH_ITERATION_RESET(lh);
235 + /* Resize the buckets array if necessary. Each expand() or
236 + * contract() call will double/halve the size of the array,
237 + * respectively, so call them in a loop. */
238 + while (LH_SHOULD_EXPAND(lh))
239 + expand(lh);
240 + while (LH_SHOULD_CONTRACT(lh))
241 + contract(lh);
242 + }
243 }
244
245 void lh_doall(_LHASH *lh, LHASH_DOALL_FN_TYPE func)
246 diff --git a/crypto/lhash/lhash.h b/crypto/lhash/lhash.h
247 index e7d8763..40aa7a5 100644
248 --- a/crypto/lhash/lhash.h
249 +++ b/crypto/lhash/lhash.h
250 @@ -163,6 +163,7 @@ typedef struct lhash_st
251 unsigned long num_hash_comps;
252
253 int error;
254 + int iteration_state;
255 } _LHASH; /* Do not use _LHASH directly, use LHASH_OF
256 * and friends */
257
258 diff --git a/crypto/objects/o_names.c b/crypto/objects/o_names.c
259 index 4a548c2..4e18045 100644
260 --- a/crypto/objects/o_names.c
261 +++ b/crypto/objects/o_names.c
262 @@ -350,13 +350,9 @@ static void name_funcs_free(NAME_FUNCS *ptr)
263
264 void OBJ_NAME_cleanup(int type)
265 {
266 - unsigned long down_load;
267 -
268 if (names_lh == NULL) return;
269
270 free_type=type;
271 - down_load=lh_OBJ_NAME_down_load(names_lh);
272 - lh_OBJ_NAME_down_load(names_lh)=0;
273
274 lh_OBJ_NAME_doall(names_lh,LHASH_DOALL_FN(names_lh_free));
275 if (type < 0)
276 @@ -366,7 +362,5 @@ void OBJ_NAME_cleanup(int type)
277 names_lh=NULL;
278 name_funcs_stack = NULL;
279 }
280 - else
281 - lh_OBJ_NAME_down_load(names_lh)=down_load;
282 }
283
284 diff --git a/crypto/objects/obj_dat.c b/crypto/objects/obj_dat.c
285 index 8a342ba..ef0512a 100644
286 --- a/crypto/objects/obj_dat.c
287 +++ b/crypto/objects/obj_dat.c
288 @@ -227,7 +227,6 @@ void OBJ_cleanup(void)
289 return ;
290 }
291 if (added == NULL) return;
292 - lh_ADDED_OBJ_down_load(added) = 0;
293 lh_ADDED_OBJ_doall(added,LHASH_DOALL_FN(cleanup1)); /* zero counters */
294 lh_ADDED_OBJ_doall(added,LHASH_DOALL_FN(cleanup2)); /* set counters */
295 lh_ADDED_OBJ_doall(added,LHASH_DOALL_FN(cleanup3)); /* free objects */
296 diff --git a/include/openssl/lhash.h b/include/openssl/lhash.h
297 index e7d8763..75386f2 100644
298 --- a/include/openssl/lhash.h
299 +++ b/include/openssl/lhash.h
300 @@ -163,6 +163,7 @@ typedef struct lhash_st
301 unsigned long num_hash_comps;
302
303 int error;
304 + int iteration_state;
305 } _LHASH; /* Do not use _LHASH directly, use LHASH_OF
306 * and friends */
307
308 diff --git a/ssl/ssl_sess.c b/ssl/ssl_sess.c
309 index 920b763..447a37e 100644
310 --- a/ssl/ssl_sess.c
311 +++ b/ssl/ssl_sess.c
312 @@ -999,11 +999,8 @@ void SSL_CTX_flush_sessions(SSL_CTX *s, long t)
313 if (tp.cache == NULL) return;
314 tp.time=t;
315 CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
316 - i=CHECKED_LHASH_OF(SSL_SESSION, tp.cache)->down_load;
317 - CHECKED_LHASH_OF(SSL_SESSION, tp.cache)->down_load=0;
318 lh_SSL_SESSION_doall_arg(tp.cache, LHASH_DOALL_ARG_FN(timeout),
319 TIMEOUT_PARAM, &tp);
320 - CHECKED_LHASH_OF(SSL_SESSION, tp.cache)->down_load=i;
321 CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
322 }
323
324 --
325 1.8.4.1
326
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698