|
1 # Source: upstream |
|
2 # https://bugs.php.net/bug.php?id=70172 |
|
3 # http://git.php.net/?p=php-src.git;a=commit;h=e8429400d40e3c3aa4b22ba701991d698a2f3b2f |
|
4 # Patch to var_unserializer.c regenerated to remove inapplicable #line comments |
|
5 |
|
6 From e8429400d40e3c3aa4b22ba701991d698a2f3b2f Mon Sep 17 00:00:00 2001 |
|
7 From: Stanislav Malyshev <[email protected]> |
|
8 Date: Mon, 31 Aug 2015 21:28:11 -0700 |
|
9 Subject: [PATCH] Fix bug #70172 - Use After Free Vulnerability in |
|
10 unserialize() |
|
11 |
|
12 --- |
|
13 ext/standard/tests/serialize/bug70172.phpt | 52 ++++++++++++++++++++ |
|
14 ext/standard/var.c | 23 +++++++-- |
|
15 ext/standard/var_unserializer.c | 76 ++++++++++++++++-------------- |
|
16 ext/standard/var_unserializer.re | 12 +++-- |
|
17 4 files changed, 121 insertions(+), 42 deletions(-) |
|
18 create mode 100644 ext/standard/tests/serialize/bug70172.phpt |
|
19 |
|
20 diff --git a/ext/standard/tests/serialize/bug70172.phpt b/ext/standard/tests/serialize/bug70172.phpt |
|
21 new file mode 100644 |
|
22 index 0000000..0e9d7ed |
|
23 --- /dev/null |
|
24 +++ b/ext/standard/tests/serialize/bug70172.phpt |
|
25 @@ -0,0 +1,52 @@ |
|
26 +--TEST-- |
|
27 +Bug #70172 - Use After Free Vulnerability in unserialize() |
|
28 +--FILE-- |
|
29 +<?php |
|
30 +class obj implements Serializable { |
|
31 + var $data; |
|
32 + function serialize() { |
|
33 + return serialize($this->data); |
|
34 + } |
|
35 + function unserialize($data) { |
|
36 + $this->data = unserialize($data); |
|
37 + } |
|
38 +} |
|
39 + |
|
40 +$fakezval = ptr2str(1122334455); |
|
41 +$fakezval .= ptr2str(0); |
|
42 +$fakezval .= "\x00\x00\x00\x00"; |
|
43 +$fakezval .= "\x01"; |
|
44 +$fakezval .= "\x00"; |
|
45 +$fakezval .= "\x00\x00"; |
|
46 + |
|
47 +$inner = 'r:2;'; |
|
48 +$exploit = 'a:2:{i:0;i:1;i:1;C:3:"obj":'.strlen($inner).':{'.$inner.'}}'; |
|
49 + |
|
50 +$data = unserialize($exploit); |
|
51 + |
|
52 +for ($i = 0; $i < 5; $i++) { |
|
53 + $v[$i] = $fakezval.$i; |
|
54 +} |
|
55 + |
|
56 +var_dump($data); |
|
57 + |
|
58 +function ptr2str($ptr) |
|
59 +{ |
|
60 + $out = ''; |
|
61 + for ($i = 0; $i < 8; $i++) { |
|
62 + $out .= chr($ptr & 0xff); |
|
63 + $ptr >>= 8; |
|
64 + } |
|
65 + return $out; |
|
66 +} |
|
67 +?> |
|
68 +--EXPECTF-- |
|
69 +array(2) { |
|
70 + [0]=> |
|
71 + int(1) |
|
72 + [1]=> |
|
73 + object(obj)#%d (1) { |
|
74 + ["data"]=> |
|
75 + int(1) |
|
76 + } |
|
77 +} |
|
78 \ No newline at end of file |
|
79 diff --git a/ext/standard/var.c b/ext/standard/var.c |
|
80 index 7603ff2..33b976f 100644 |
|
81 --- a/ext/standard/var.c |
|
82 +++ b/ext/standard/var.c |
|
83 @@ -373,7 +373,7 @@ static int php_array_element_export(zval **zv TSRMLS_DC, int num_args, va_list a |
|
84 |
|
85 smart_str_appendc(buf, ','); |
|
86 smart_str_appendc(buf, '\n'); |
|
87 - |
|
88 + |
|
89 return 0; |
|
90 } |
|
91 /* }}} */ |
|
92 @@ -392,7 +392,7 @@ static int php_object_element_export(zval **zv TSRMLS_DC, int num_args, va_list |
|
93 const char *pname; |
|
94 char *pname_esc; |
|
95 int pname_esc_len; |
|
96 - |
|
97 + |
|
98 zend_unmangle_property_name(hash_key->arKey, hash_key->nKeyLength - 1, |
|
99 &class_name, &pname); |
|
100 pname_esc = php_addcslashes(pname, strlen(pname), &pname_esc_len, 0, |
|
101 @@ -469,7 +469,7 @@ PHPAPI void php_var_export_ex(zval **struc, int level, smart_str *buf TSRMLS_DC) |
|
102 buffer_append_spaces(buf, level - 1); |
|
103 } |
|
104 smart_str_appendc(buf, ')'); |
|
105 - |
|
106 + |
|
107 break; |
|
108 |
|
109 case IS_OBJECT: |
|
110 @@ -802,7 +802,7 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, HashTable *var |
|
111 BG(serialize_lock)++; |
|
112 res = call_user_function_ex(CG(function_table), &struc, &fname, &retval_ptr, 0, 0, 1, NULL TSRMLS_CC); |
|
113 BG(serialize_lock)--; |
|
114 - |
|
115 + |
|
116 if (EG(exception)) { |
|
117 if (retval_ptr) { |
|
118 zval_ptr_dtor(&retval_ptr); |
|
119 @@ -951,6 +951,8 @@ PHP_FUNCTION(unserialize) |
|
120 int buf_len; |
|
121 const unsigned char *p; |
|
122 php_unserialize_data_t var_hash; |
|
123 + int oldlevel; |
|
124 + zval *old_rval = return_value; |
|
125 |
|
126 if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &buf, &buf_len) == FAILURE) { |
|
127 RETURN_FALSE; |
|
128 @@ -970,6 +972,19 @@ PHP_FUNCTION(unserialize) |
|
129 } |
|
130 RETURN_FALSE; |
|
131 } |
|
132 + if (return_value != old_rval) { |
|
133 + /* |
|
134 + * Terrible hack due to the fact that executor passes us zval *, |
|
135 + * but unserialize with r/R wants to replace it with another zval * |
|
136 + */ |
|
137 + zval_dtor(old_rval); |
|
138 + *old_rval = *return_value; |
|
139 + zval_copy_ctor(old_rval); |
|
140 + var_push_dtor_no_addref(&var_hash, &return_value); |
|
141 + var_push_dtor_no_addref(&var_hash, &old_rval); |
|
142 + } else { |
|
143 + var_push_dtor(&var_hash, &return_value); |
|
144 + } |
|
145 PHP_VAR_UNSERIALIZE_DESTROY(var_hash); |
|
146 } |
|
147 /* }}} */ |
|
148 --- var_unserializer.c.orig Wed Sep 9 13:59:27 2015 |
|
149 +++ php-5.6.8/ext/standard/var_unserializer.c Wed Sep 9 14:02:03 2015 |
|
150 @@ -67,7 +67,7 @@ |
|
151 |
|
152 var_hash = (*var_hashx)->last_dtor; |
|
153 #if VAR_ENTRIES_DBG |
|
154 - fprintf(stderr, "var_push_dtor(%ld): %d\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval)); |
|
155 + fprintf(stderr, "var_push_dtor(%p, %ld): %d\n", *rval, var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval)); |
|
156 #endif |
|
157 |
|
158 if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) { |
|
159 @@ -92,7 +92,7 @@ |
|
160 { |
|
161 var_entries *var_hash = (*var_hashx)->last_dtor; |
|
162 #if VAR_ENTRIES_DBG |
|
163 - fprintf(stderr, "var_push_dtor_no_addref(%ld): %d (%d)\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval), Z_REFCOUNT_PP(rval)); |
|
164 + fprintf(stderr, "var_push_dtor_no_addref(%p, %ld): %d (%d)\n", *rval, var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval), Z_REFCOUNT_PP(rval)); |
|
165 #endif |
|
166 |
|
167 if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) { |
|
168 @@ -171,6 +171,9 @@ |
|
169 |
|
170 while (var_hash) { |
|
171 for (i = 0; i < var_hash->used_slots; i++) { |
|
172 +#if VAR_ENTRIES_DBG |
|
173 + fprintf(stderr, "var_destroy dtor(%p, %ld)\n", var_hash->data[i], Z_REFCOUNT_P(var_hash->data[i])); |
|
174 +#endif |
|
175 zval_ptr_dtor(&var_hash->data[i]); |
|
176 } |
|
177 next = var_hash->next; |
|
178 @@ -628,6 +631,7 @@ |
|
179 zval **args[1]; |
|
180 zval *arg_func_name; |
|
181 |
|
182 + if (!var_hash) return 0; |
|
183 if (*start == 'C') { |
|
184 custom_object = 1; |
|
185 } |
|
186 @@ -783,6 +787,7 @@ |
|
187 if (yych != '"') goto yy18; |
|
188 ++YYCURSOR; |
|
189 { |
|
190 + if (!var_hash) return 0; |
|
191 |
|
192 INIT_PZVAL(*rval); |
|
193 |
|
194 @@ -813,6 +818,7 @@ |
|
195 long elements = parse_iv(start + 2); |
|
196 /* use iv() not uiv() in order to check data range */ |
|
197 *p = YYCURSOR; |
|
198 + if (!var_hash) return 0; |
|
199 |
|
200 if (elements < 0) { |
|
201 return 0; |
|
202 @@ -1242,7 +1248,7 @@ |
|
203 } |
|
204 |
|
205 if (*rval != NULL) { |
|
206 - zval_ptr_dtor(rval); |
|
207 + var_push_dtor_no_addref(var_hash, rval); |
|
208 } |
|
209 *rval = *rval_ref; |
|
210 Z_ADDREF_PP(rval); |
|
211 diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re |
|
212 index f02602c..ed82152 100644 |
|
213 --- a/ext/standard/var_unserializer.re |
|
214 +++ b/ext/standard/var_unserializer.re |
|
215 @@ -67,7 +67,7 @@ PHPAPI void var_push_dtor(php_unserialize_data_t *var_hashx, zval **rval) |
|
216 |
|
217 var_hash = (*var_hashx)->last_dtor; |
|
218 #if VAR_ENTRIES_DBG |
|
219 - fprintf(stderr, "var_push_dtor(%ld): %d\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval)); |
|
220 + fprintf(stderr, "var_push_dtor(%p, %ld): %d\n", *rval, var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval)); |
|
221 #endif |
|
222 |
|
223 if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) { |
|
224 @@ -98,7 +98,7 @@ PHPAPI void var_push_dtor_no_addref(php_unserialize_data_t *var_hashx, zval **rv |
|
225 |
|
226 var_hash = (*var_hashx)->last_dtor; |
|
227 #if VAR_ENTRIES_DBG |
|
228 - fprintf(stderr, "var_push_dtor_no_addref(%ld): %d (%d)\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval), Z_REFCOUNT_PP(rval)); |
|
229 + fprintf(stderr, "var_push_dtor_no_addref(%p, %ld): %d (%d)\n", *rval, var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval), Z_REFCOUNT_PP(rval)); |
|
230 #endif |
|
231 |
|
232 if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) { |
|
233 @@ -177,6 +177,9 @@ PHPAPI void var_destroy(php_unserialize_data_t *var_hashx) |
|
234 |
|
235 while (var_hash) { |
|
236 for (i = 0; i < var_hash->used_slots; i++) { |
|
237 +#if VAR_ENTRIES_DBG |
|
238 + fprintf(stderr, "var_destroy dtor(%p, %ld)\n", var_hash->data[i], Z_REFCOUNT_P(var_hash->data[i])); |
|
239 +#endif |
|
240 zval_ptr_dtor(&var_hash->data[i]); |
|
241 } |
|
242 next = var_hash->next; |
|
243 @@ -501,7 +504,7 @@ PHPAPI int php_var_unserialize(UNSERIALIZE_PARAMETER) |
|
244 } |
|
245 |
|
246 if (*rval != NULL) { |
|
247 - zval_ptr_dtor(rval); |
|
248 + var_push_dtor_no_addref(var_hash, rval); |
|
249 } |
|
250 *rval = *rval_ref; |
|
251 Z_ADDREF_PP(rval); |
|
252 @@ -660,6 +663,7 @@ use_double: |
|
253 long elements = parse_iv(start + 2); |
|
254 /* use iv() not uiv() in order to check data range */ |
|
255 *p = YYCURSOR; |
|
256 + if (!var_hash) return 0; |
|
257 |
|
258 if (elements < 0) { |
|
259 return 0; |
|
260 @@ -677,6 +681,7 @@ use_double: |
|
261 } |
|
262 |
|
263 "o:" iv ":" ["] { |
|
264 + if (!var_hash) return 0; |
|
265 |
|
266 INIT_PZVAL(*rval); |
|
267 |
|
268 @@ -699,6 +704,7 @@ object ":" uiv ":" ["] { |
|
269 zval **args[1]; |
|
270 zval *arg_func_name; |
|
271 |
|
272 + if (!var_hash) return 0; |
|
273 if (*start == 'C') { |
|
274 custom_object = 1; |
|
275 } |
|
276 -- |
|
277 2.1.4 |
|
278 |
|
279 |