Backport of: From df4bf28f9f104ca3ef78ed94b497859f15b004e5 Mon Sep 17 00:00:00 2001 From: Stanislav Malyshev Date: Sun, 23 Aug 2015 13:27:59 -0700 Subject: [PATCH] Fix bug #70219 (Use after free vulnerability in session deserializer) --- ext/session/session.c | 36 +- ext/session/tests/session_decode_error2.phpt | 518 +++++------------------ ext/session/tests/session_decode_variation3.phpt | 2 +- ext/standard/tests/serialize/bug70219.phpt | 38 ++ ext/standard/var_unserializer.c | 68 +-- ext/standard/var_unserializer.re | 64 +-- 6 files changed, 228 insertions(+), 498 deletions(-) create mode 100644 ext/standard/tests/serialize/bug70219.phpt Index: php5-5.6.11+dfsg/ext/session/session.c =================================================================== --- php5-5.6.11+dfsg.orig/ext/session/session.c 2015-09-25 11:15:07.144015828 -0400 +++ php5-5.6.11+dfsg/ext/session/session.c 2015-09-25 11:15:07.140015773 -0400 @@ -216,16 +216,18 @@ } /* }}} */ -static void php_session_decode(const char *val, int vallen TSRMLS_DC) /* {{{ */ +static int php_session_decode(const char *val, int vallen TSRMLS_DC) /* {{{ */ { if (!PS(serializer)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown session.serialize_handler. Failed to decode session object"); - return; + return FAILURE; } if (PS(serializer)->decode(val, vallen TSRMLS_CC) == FAILURE) { php_session_destroy(TSRMLS_C); php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to decode session object. Session has been destroyed"); + return FAILURE; } + return SUCCESS; } /* }}} */ @@ -947,8 +949,11 @@ ALLOC_INIT_ZVAL(current); if (php_var_unserialize(¤t, (const unsigned char **) &p, (const unsigned char *) endptr, &var_hash TSRMLS_CC)) { php_set_session_var(name, namelen, current, &var_hash TSRMLS_CC); + } else { + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + return FAILURE; } - zval_ptr_dtor(¤t); + var_push_dtor_no_addref(&var_hash, ¤t); } PS_ADD_VARL(name, namelen); efree(name); @@ -1039,8 +1044,13 @@ ALLOC_INIT_ZVAL(current); if (php_var_unserialize(¤t, (const unsigned char **) &q, (const unsigned char *) endptr, &var_hash TSRMLS_CC)) { php_set_session_var(name, namelen, current, &var_hash TSRMLS_CC); + } else { + var_push_dtor_no_addref(&var_hash, ¤t); + efree(name); + PHP_VAR_UNSERIALIZE_DESTROY(var_hash); + return FAILURE; } - zval_ptr_dtor(¤t); + var_push_dtor_no_addref(&var_hash, ¤t); } PS_ADD_VARL(name, namelen); skip: @@ -2063,9 +2073,7 @@ return; } - php_session_decode(str, str_len TSRMLS_CC); - - RETURN_TRUE; + RETVAL_BOOL(php_session_decode(str, str_len TSRMLS_CC) == SUCCESS); } /* }}} */ Index: php5-5.6.11+dfsg/ext/session/tests/session_decode_error2.phpt =================================================================== --- php5-5.6.11+dfsg.orig/ext/session/tests/session_decode_error2.phpt 2015-09-25 11:15:07.144015828 -0400 +++ php5-5.6.11+dfsg/ext/session/tests/session_decode_error2.phpt 2015-09-25 11:15:07.140015773 -0400 @@ -53,563 +53,247 @@ } -- Iteration 4 -- -bool(true) -array(1) { - ["foo"]=> - NULL + +Warning: session_decode(): Failed to decode session object. Session has been destroyed in %s/session_decode_error2.php on line %d +bool(false) +array(0) { } -- Iteration 5 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 6 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 7 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 8 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 9 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 10 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 11 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 12 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 13 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 14 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 15 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 16 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 17 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 18 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 19 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 20 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 21 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 22 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 23 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 24 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 25 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 26 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 27 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 28 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 29 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 30 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 31 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 32 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 33 -- -bool(true) -array(1) { - ["foo"]=> - NULL +bool(false) +array(0) { } -- Iteration 34 -- -bool(true) -array(1) { - ["foo"]=> - array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } +bool(false) +array(0) { } -- Iteration 35 -- -bool(true) -array(1) { - ["foo"]=> - array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } +bool(false) +array(0) { } -- Iteration 36 -- -bool(true) -array(1) { - ["foo"]=> - array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } +bool(false) +array(0) { } -- Iteration 37 -- -bool(true) -array(1) { - ["foo"]=> - array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } +bool(false) +array(0) { } -- Iteration 38 -- -bool(true) -array(1) { - ["foo"]=> - array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } +bool(false) +array(0) { } -- Iteration 39 -- -bool(true) -array(2) { - ["foo"]=> - array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - NULL +bool(false) +array(0) { } -- Iteration 40 -- -bool(true) -array(2) { - ["foo"]=> - array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - NULL +bool(false) +array(0) { } -- Iteration 41 -- -bool(true) -array(2) { - ["foo"]=> - array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - NULL +bool(false) +array(0) { } -- Iteration 42 -- -bool(true) -array(2) { - ["foo"]=> - array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - NULL +bool(false) +array(0) { } -- Iteration 43 -- -bool(true) -array(2) { - ["foo"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } +bool(false) +array(0) { } -- Iteration 44 -- -bool(true) -array(2) { - ["foo"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } +bool(false) +array(0) { } -- Iteration 45 -- -bool(true) -array(2) { - ["foo"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } +bool(false) +array(0) { } -- Iteration 46 -- -bool(true) -array(2) { - ["foo"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } +bool(false) +array(0) { } -- Iteration 47 -- -bool(true) -array(2) { - ["foo"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } +bool(false) +array(0) { } -- Iteration 48 -- -bool(true) -array(3) { - ["foo"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["blah"]=> - NULL +bool(false) +array(0) { } -- Iteration 49 -- -bool(true) -array(3) { - ["foo"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["blah"]=> - NULL +bool(false) +array(0) { } -- Iteration 50 -- -bool(true) -array(3) { - ["foo"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["blah"]=> - NULL +bool(false) +array(0) { } -- Iteration 51 -- -bool(true) -array(3) { - ["foo"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["guff"]=> - &array(3) { - [0]=> - int(1) - [1]=> - int(2) - [2]=> - int(3) - } - ["blah"]=> - NULL +bool(false) +array(0) { } -bool(true) -Done +Warning: session_destroy(): Trying to destroy uninitialized session in %s/session_decode_error2.php on line %d +bool(false) +Done Index: php5-5.6.11+dfsg/ext/session/tests/session_decode_variation3.phpt =================================================================== --- php5-5.6.11+dfsg.orig/ext/session/tests/session_decode_variation3.phpt 2015-09-25 11:15:07.144015828 -0400 +++ php5-5.6.11+dfsg/ext/session/tests/session_decode_variation3.phpt 2015-09-25 11:15:07.140015773 -0400 @@ -49,7 +49,7 @@ } Warning: session_decode(): Unknown session.serialize_handler. Failed to decode session object in %s on line %d -bool(true) +bool(false) array(3) { ["foo"]=> int(1234567890) 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:15:07.144015828 -0400 +++ php5-5.6.11+dfsg/ext/standard/var_unserializer.c 2015-09-25 11:16:22.569039413 -0400 @@ -90,7 +90,13 @@ PHPAPI void var_push_dtor_no_addref(php_unserialize_data_t *var_hashx, zval **rval) { - var_entries *var_hash = (*var_hashx)->last_dtor; + var_entries *var_hash; + + if (!var_hashx || !*var_hashx) { + return; + } + + var_hash = (*var_hashx)->last_dtor; #if VAR_ENTRIES_DBG 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 @@ -304,23 +310,20 @@ ALLOC_INIT_ZVAL(key); if (!php_var_unserialize(&key, p, max, NULL TSRMLS_CC)) { - zval_dtor(key); - FREE_ZVAL(key); + var_push_dtor_no_addref(var_hash, &key); return 0; } if (Z_TYPE_P(key) != IS_LONG && Z_TYPE_P(key) != IS_STRING) { - zval_dtor(key); - FREE_ZVAL(key); + var_push_dtor_no_addref(var_hash, &key); return 0; } ALLOC_INIT_ZVAL(data); if (!php_var_unserialize(&data, p, max, var_hash TSRMLS_CC)) { - zval_dtor(key); - FREE_ZVAL(key); - zval_ptr_dtor(&data); + var_push_dtor_no_addref(var_hash, &key); + var_push_dtor_no_addref(var_hash, &data); return 0; } @@ -349,9 +352,7 @@ sizeof data, NULL); } var_push_dtor(var_hash, &data); - - zval_dtor(key); - FREE_ZVAL(key); + var_push_dtor_no_addref(var_hash, &key); if (elements && *(*p-1) != ';' && *(*p-1) != '}') { (*p)--; 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:15:07.144015828 -0400 +++ php5-5.6.11+dfsg/ext/standard/var_unserializer.re 2015-09-25 11:16:58.729527176 -0400 @@ -89,7 +89,13 @@ PHPAPI void var_push_dtor_no_addref(php_unserialize_data_t *var_hashx, zval **rval) { - var_entries *var_hash = (*var_hashx)->last_dtor; + var_entries *var_hash; + + if (!var_hashx || !*var_hashx) { + return; + } + + var_hash = (*var_hashx)->last_dtor; #if VAR_ENTRIES_DBG 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 @@ -310,23 +316,20 @@ ALLOC_INIT_ZVAL(key); if (!php_var_unserialize(&key, p, max, NULL TSRMLS_CC)) { - zval_dtor(key); - FREE_ZVAL(key); + var_push_dtor_no_addref(var_hash, &key); return 0; } if (Z_TYPE_P(key) != IS_LONG && Z_TYPE_P(key) != IS_STRING) { - zval_dtor(key); - FREE_ZVAL(key); + var_push_dtor_no_addref(var_hash, &key); return 0; } ALLOC_INIT_ZVAL(data); if (!php_var_unserialize(&data, p, max, var_hash TSRMLS_CC)) { - zval_dtor(key); - FREE_ZVAL(key); - zval_ptr_dtor(&data); + var_push_dtor_no_addref(var_hash, &key); + var_push_dtor_no_addref(var_hash, &data); return 0; } @@ -355,9 +358,7 @@ sizeof data, NULL); } var_push_dtor(var_hash, &data); - - zval_dtor(key); - FREE_ZVAL(key); + var_push_dtor_no_addref(var_hash, &key); if (elements && *(*p-1) != ';' && *(*p-1) != '}') { (*p)--;