Backport of: From e8429400d40e3c3aa4b22ba701991d698a2f3b2f Mon Sep 17 00:00:00 2001 From: Stanislav Malyshev Date: Mon, 31 Aug 2015 21:28:11 -0700 Subject: [PATCH] Fix bug #70172 - Use After Free Vulnerability in unserialize() --- ext/standard/tests/serialize/bug70172.phpt | 52 ++++++++++++++++++++ ext/standard/var.c | 23 +++++++-- ext/standard/var_unserializer.c | 76 ++++++++++++++++-------------- ext/standard/var_unserializer.re | 12 +++-- 4 files changed, 121 insertions(+), 42 deletions(-) create mode 100644 ext/standard/tests/serialize/bug70172.phpt Index: php5-5.6.11+dfsg/ext/standard/tests/serialize/bug70172.phpt =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ php5-5.6.11+dfsg/ext/standard/tests/serialize/bug70172.phpt 2015-09-25 11:09:09.327373219 -0400 @@ -0,0 +1,52 @@ +--TEST-- +Bug #70172 - Use After Free Vulnerability in unserialize() +--FILE-- +data); + } + function unserialize($data) { + $this->data = unserialize($data); + } +} + +$fakezval = ptr2str(1122334455); +$fakezval .= ptr2str(0); +$fakezval .= "\x00\x00\x00\x00"; +$fakezval .= "\x01"; +$fakezval .= "\x00"; +$fakezval .= "\x00\x00"; + +$inner = 'r:2;'; +$exploit = 'a:2:{i:0;i:1;i:1;C:3:"obj":'.strlen($inner).':{'.$inner.'}}'; + +$data = unserialize($exploit); + +for ($i = 0; $i < 5; $i++) { + $v[$i] = $fakezval.$i; +} + +var_dump($data); + +function ptr2str($ptr) +{ + $out = ''; + for ($i = 0; $i < 8; $i++) { + $out .= chr($ptr & 0xff); + $ptr >>= 8; + } + return $out; +} +?> +--EXPECTF-- +array(2) { + [0]=> + int(1) + [1]=> + object(obj)#%d (1) { + ["data"]=> + int(1) + } +} \ No newline at end of file Index: php5-5.6.11+dfsg/ext/standard/var.c =================================================================== --- php5-5.6.11+dfsg.orig/ext/standard/var.c 2015-09-25 11:09:09.331373265 -0400 +++ php5-5.6.11+dfsg/ext/standard/var.c 2015-09-25 11:09:09.327373219 -0400 @@ -948,6 +948,8 @@ int buf_len; const unsigned char *p; php_unserialize_data_t var_hash; + int oldlevel; + zval *old_rval = return_value; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &buf, &buf_len) == FAILURE) { RETURN_FALSE; @@ -967,6 +969,19 @@ } RETURN_FALSE; } + if (return_value != old_rval) { + /* + * Terrible hack due to the fact that executor passes us zval *, + * but unserialize with r/R wants to replace it with another zval * + */ + zval_dtor(old_rval); + *old_rval = *return_value; + zval_copy_ctor(old_rval); + var_push_dtor_no_addref(&var_hash, &return_value); + var_push_dtor_no_addref(&var_hash, &old_rval); + } else { + var_push_dtor(&var_hash, &return_value); + } PHP_VAR_UNSERIALIZE_DESTROY(var_hash); } /* }}} */ Index: php5-5.6.11+dfsg/ext/standard/var_unserializer.c =================================================================== --- php5-5.6.11+dfsg.orig/ext/standard/var_unserializer.c 2015-09-25 11:09:09.331373265 -0400 +++ php5-5.6.11+dfsg/ext/standard/var_unserializer.c 2015-09-25 11:10:02.207978317 -0400 @@ -67,7 +67,7 @@ var_hash = (*var_hashx)->last_dtor; #if VAR_ENTRIES_DBG - fprintf(stderr, "var_push_dtor(%ld): %d\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval)); + fprintf(stderr, "var_push_dtor(%p, %ld): %d\n", *rval, var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval)); #endif if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) { @@ -92,7 +92,7 @@ { var_entries *var_hash = (*var_hashx)->last_dtor; #if VAR_ENTRIES_DBG - 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)); + 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)); #endif if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) { @@ -171,6 +171,9 @@ while (var_hash) { for (i = 0; i < var_hash->used_slots; i++) { +#if VAR_ENTRIES_DBG + fprintf(stderr, "var_destroy dtor(%p, %ld)\n", var_hash->data[i], Z_REFCOUNT_P(var_hash->data[i])); +#endif zval_ptr_dtor(&var_hash->data[i]); } next = var_hash->next; @@ -628,6 +631,7 @@ zval **args[1]; zval *arg_func_name; + if (!var_hash) return 0; if (*start == 'C') { custom_object = 1; } @@ -783,6 +787,7 @@ if (yych != '"') goto yy18; ++YYCURSOR; { + if (!var_hash) return 0; INIT_PZVAL(*rval); @@ -813,6 +818,7 @@ long elements = parse_iv(start + 2); /* use iv() not uiv() in order to check data range */ *p = YYCURSOR; + if (!var_hash) return 0; if (elements < 0) { return 0; @@ -1242,7 +1248,7 @@ } if (*rval != NULL) { - zval_ptr_dtor(rval); + var_push_dtor_no_addref(var_hash, rval); } *rval = *rval_ref; Z_ADDREF_PP(rval); Index: php5-5.6.11+dfsg/ext/standard/var_unserializer.re =================================================================== --- php5-5.6.11+dfsg.orig/ext/standard/var_unserializer.re 2015-09-25 11:09:09.331373265 -0400 +++ php5-5.6.11+dfsg/ext/standard/var_unserializer.re 2015-09-25 11:09:09.327373219 -0400 @@ -66,7 +66,7 @@ var_hash = (*var_hashx)->last_dtor; #if VAR_ENTRIES_DBG - fprintf(stderr, "var_push_dtor(%ld): %d\n", var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval)); + fprintf(stderr, "var_push_dtor(%p, %ld): %d\n", *rval, var_hash?var_hash->used_slots:-1L, Z_TYPE_PP(rval)); #endif if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) { @@ -91,7 +91,7 @@ { var_entries *var_hash = (*var_hashx)->last_dtor; #if VAR_ENTRIES_DBG - 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)); + 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)); #endif if (!var_hash || var_hash->used_slots == VAR_ENTRIES_MAX) { @@ -170,6 +170,9 @@ while (var_hash) { for (i = 0; i < var_hash->used_slots; i++) { +#if VAR_ENTRIES_DBG + fprintf(stderr, "var_destroy dtor(%p, %ld)\n", var_hash->data[i], Z_REFCOUNT_P(var_hash->data[i])); +#endif zval_ptr_dtor(&var_hash->data[i]); } next = var_hash->next; @@ -495,7 +498,7 @@ } if (*rval != NULL) { - zval_ptr_dtor(rval); + var_push_dtor_no_addref(var_hash, rval); } *rval = *rval_ref; Z_ADDREF_PP(rval); @@ -654,6 +657,7 @@ long elements = parse_iv(start + 2); /* use iv() not uiv() in order to check data range */ *p = YYCURSOR; + if (!var_hash) return 0; if (elements < 0) { return 0; @@ -671,6 +675,7 @@ } "o:" iv ":" ["] { + if (!var_hash) return 0; INIT_PZVAL(*rval); @@ -693,6 +698,7 @@ zval **args[1]; zval *arg_func_name; + if (!var_hash) return 0; if (*start == 'C') { custom_object = 1; }