php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Sec Bug #71313 Use-after-free vulnerability in SPL(SplObjectStorage, unserialize)
Submitted: 2016-01-08 14:02 UTC Modified: 2016-02-02 05:20 UTC
From: sean dot heelan at gmail dot com Assigned: stas
Status: Closed Package: SPL related
PHP Version: 7.* OS: Ubuntu 15.10
Private report: No CVE-ID:
 [2016-01-08 14:02 UTC] sean dot heelan at gmail dot com
Description:
------------
Summary
-------

By correctly crafting their input to `unserialize` an attacker can cause the
`SPL_METHOD(SplObjectStorage, unserialize)` to pass destroyed `zval`s to
`php_var_unserialize`. This is the same insecure pattern, and has similar root
causes, to #71311, and so I won't go into too much detail here.

Impact
------

This vulnerability can potentially be leveraged to achieve code execution. Any
application that calls `unserialize` on user provided data is potentially
vulnerable.

Patch
-----

The attached patch modifies `SPL_METHOD(SplObjectStorage, unserialize)` to mark
the destroyed `zval`s as undefined, immediately after they are destroyed. As
described in #71311, to fix the root cause a bug in `php_var_unserialize_ex`
should also be fixed. See that issue for the mentioned patch.

Crash Details
-------------
$ gdb -q ./sapi/cli/php

(gdb) r deserialise.php poc.sz
Starting program: /home/sean/Git/php-src/sapi/cli/php deserialise.php poc.sz
C:16:"SplObjectStorage":113:{x:i:2;O:8:"stdClass":0:{},a:2:{s:4:"prev";i:2;s:4:"next";O:8:"stdClass":0:{}};r:7;,R:2;s:4:"next";;r:3;};m:a:0:{}}

Program received signal SIGSEGV, Segmentation fault.
0x000000000084e570 in zend_mm_alloc_small (bin_num=6, size=56, heap=0x7ffff4800040) at /home/sean/Git/php-src/Zend/zend_alloc.c:1291
1291			heap->free_slot[bin_num] = p->next_free_slot;

(gdb) bt
#0  0x000000000084e570 in zend_mm_alloc_small (bin_num=6, size=56, heap=0x7ffff4800040) at /home/sean/Git/php-src/Zend/zend_alloc.c:1291
#1  _emalloc_56 () at /home/sean/Git/php-src/Zend/zend_alloc.c:2361
#2  0x0000000000897dd2 in _array_init (arg=0x7fffffff9380, size=0) at /home/sean/Git/php-src/Zend/zend_API.c:1063
#3  0x00000000008cbaa5 in zend_fetch_debug_backtrace (return_value=0x7fffffff9410, skip_last=0, options=0, limit=0)
    at /home/sean/Git/php-src/Zend/zend_builtin_functions.c:2528
#4  0x00000000008d3fa2 in zend_default_exception_new_ex (class_type=0x11fdb10, skip_top_traces=0) at /home/sean/Git/php-src/Zend/zend_exceptions.c:213
#5  0x00000000008d411e in zend_default_exception_new (class_type=0x11fdb10) at /home/sean/Git/php-src/Zend/zend_exceptions.c:236
#6  0x0000000000898ea3 in _object_and_properties_init (arg=0x7fffffff94e0, class_type=0x11fdb10, properties=0x0) at /home/sean/Git/php-src/Zend/zend_API.c:1304
#7  0x0000000000898ee7 in _object_init_ex (arg=0x7fffffff94e0, class_type=0x11fdb10) at /home/sean/Git/php-src/Zend/zend_API.c:1312
#8  0x00000000008dbf98 in zend_throw_exception (exception_ce=0x11fdb10, message=0x7ffff486a140 "Error at offset 87 of 113 bytes", code=0)
    at /home/sean/Git/php-src/Zend/zend_exceptions.c:878
#9  0x00000000008dc0f0 in zend_throw_exception_ex (exception_ce=0x11fdb10, code=0, format=0xdff510 "Error at offset %pd of %d bytes")
    at /home/sean/Git/php-src/Zend/zend_exceptions.c:902
#10 0x00000000006e035d in zim_spl_SplObjectStorage_unserialize (execute_data=0x7ffff48141f0, return_value=0x7fffffff9b10)
    at /home/sean/Git/php-src/ext/spl/spl_observer.c:854
#11 0x0000000000875e3d in zend_call_function (fci=0x7fffffff9b70, fci_cache=0x7fffffff9b40) at /home/sean/Git/php-src/Zend/zend_execute_API.c:879
#12 0x00000000008d1655 in zend_call_method (object=0x7ffff4814130, obj_ce=0x1229bf0, fn_proxy=0x1229d38, function_name=0xe36405 "unserialize", function_name_len=11,
    retval_ptr=0x0, param_count=1, arg1=0x7fffffff9c70, arg2=0x0) at /home/sean/Git/php-src/Zend/zend_interfaces.c:104
#13 0x00000000008d2834 in zend_user_unserialize (object=0x7ffff4814130, ce=0x1229bf0,
    buf=0x7ffff4877335 "x:i:2;O:8:\"stdClass\":0:{},a:2:{s:4:\"prev\";i:2;s:4:\"next\";O:8:\"stdClass\":0:{}};r:7;,R:2;s:4:\"next\";;r:3;};m:a:0:{}}\n", buf_len=113,
    data=0x7fffffffa148) at /home/sean/Git/php-src/Zend/zend_interfaces.c:460
#14 0x00000000007b0526 in object_custom (rval=0x7ffff4814130, p=0x7fffffffa140, max=0x7ffff48773a8 "", var_hash=0x7fffffffa148, classes=0x0, ce=0x1229bf0)
    at ext/standard/var_unserializer.re:425
#15 0x00000000007b21bd in php_var_unserialize_ex (rval=0x7ffff4814130, p=0x7fffffffa140, max=0x7ffff48773a8 "", var_hash=0x7fffffffa148, classes=0x0)
    at ext/standard/var_unserializer.re:844
#16 0x000000000079b069 in zif_unserialize (execute_data=0x7ffff4814180, return_value=0x7ffff4814130) at /home/sean/Git/php-src/ext/standard/var.c:1038
#17 0x0000000000924373 in ZEND_DO_ICALL_SPEC_HANDLER () at /home/sean/Git/php-src/Zend/zend_vm_execute.h:586
#18 0x0000000000922aee in execute_ex (ex=0x7ffff4814030) at /home/sean/Git/php-src/Zend/zend_vm_execute.h:414
#19 0x0000000000923464 in zend_execute (op_array=0x7ffff4882000, return_value=0x0) at /home/sean/Git/php-src/Zend/zend_vm_execute.h:458
#20 0x00000000008931c5 in zend_execute_scripts (type=8, retval=0x0, file_count=3) at /home/sean/Git/php-src/Zend/zend.c:1427
#21 0x00000000007e2400 in php_execute_script (primary_file=0x7fffffffcb30) at /home/sean/Git/php-src/main/main.c:2484
#22 0x0000000000a0109e in do_cli (argc=3, argv=0x1169530) at /home/sean/Git/php-src/sapi/cli/php_cli.c:974
#23 0x0000000000a02462 in main (argc=3, argv=0x1169530) at /home/sean/Git/php-src/sapi/cli/php_cli.c:1345
(gdb) x/i $rip
=> 0x84e570 <_emalloc_56+225>:	mov    (%rax),%rdx
(gdb) i r
rax            0xc0047ffff4857380	-4610419381224770688
rbx            0x0	0
rcx            0x18	24
rdx            0xa	10
rsi            0x0	0
rdi            0x7fffffff9380	140737488327552
rbp            0x7fffffff9270	0x7fffffff9270
rsp            0x7fffffff9240	0x7fffffff9240
r8             0x7fffffff8b88	140737488325512
r9             0x0	0
r10            0x22b	555
r11            0x7ffff70b3e30	140737338097200
r12            0x423020	4337696
r13            0x7fffffffdeb0	140737488346800
r14            0x7ffff4814030	140737295499312
r15            0x7ffff4889220	140737295979040
rip            0x84e570	0x84e570 <_emalloc_56+225>
eflags         0x10206	[ PF IF RF ]
cs             0x33	51
ss             0x2b	43
ds             0x0	0
es             0x0	0
fs             0x0	0
gs             0x0	0

EOF

Test script:
---------------
<?php
$data = unserialize("C:16:\"SplObjectStorage\":113:{x:i:2;O:8:\"stdClass\":0:{},a:2:{s:4:\"prev\";i:2;s:4:\"next\";O:8:\"stdClass\":0:{}};r:7;,R:2;s:4:\"next\";;r:3;};m:a:0:{}}");
var_dump($data);
?>

Expected result:
----------------
Not a segfault.

Actual result:
--------------
A segfault.

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2016-01-08 19:24 UTC] stas@php.net
5.5 and 5.6 produce  Uncaught exception 'UnexpectedValueException' with message 'Error at offset 82 of 113 bytes', but 7.x segfaults. So it seems to be 7.x problem.
 [2016-01-08 22:05 UTC] stas@php.net
-PHP Version: master-Git-2016-01-08 (Git) +PHP Version: 7.*
 [2016-01-08 22:16 UTC] sean dot heelan at gmail dot com
Hi Stas,

Yes, it only affects version 7.0 due to changes made in var_unserializer.re. See my comments and patch to #71311. A check was removed in 7.0 which in earlier versions prevented a reference item in a serialized string from referring to itself.
 [2016-01-08 22:17 UTC] sean dot heelan at gmail dot com
No idea why, but the patches I added via the bug submission form don't seem to be appearing here. Anyway, here is the patch. See #71311 for the fix for var_unserializer.re.


From 6e8400cd029b59229552bc9af237e67ae38c8811 Mon Sep 17 00:00:00 2001
From: Sean Heelan <sean.heelan@gmail.com>
Date: Thu, 7 Jan 2016 13:46:16 +0000
Subject: [PATCH] Mark the entry and inf zval's as undefined after their
 references have been released

---
 ext/spl/spl_observer.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c
index 154a3c0..e8d6074 100644
--- a/ext/spl/spl_observer.c
+++ b/ext/spl/spl_observer.c
@@ -821,7 +821,9 @@ SPL_METHOD(SplObjectStorage, unserialize)
 		var_replace(&var_hash, &entry, &element->obj);
 		var_replace(&var_hash, &inf, &element->inf);
 		zval_ptr_dtor(&entry);
+		ZVAL_UNDEF(&entry);
 		zval_ptr_dtor(&inf);
+		ZVAL_UNDEF(&inf);
 	}

 	if (*p != ';') {
--
2.1.4
 [2016-01-18 07:24 UTC] stas@php.net
-Assigned To: +Assigned To: stas
 [2016-01-18 07:24 UTC] stas@php.net
Added to security repo as 52e0c4081f8454e9086fc7d1bd1a338ac4e05868
 [2016-02-02 05:20 UTC] stas@php.net
-Status: Assigned +Status: Closed
 [2016-02-02 05:20 UTC] stas@php.net
The fix for this bug has been committed.

Snapshots of the sources are packaged every three hours; this change
will be in the next snapshot. You can grab the snapshot at
http://snaps.php.net/.

 For Windows:

http://windows.php.net/snapshots/
 
Thank you for the report, and for helping us make PHP better.


 [2016-07-20 11:34 UTC] davey@php.net
Automatic comment on behalf of stas
Revision: http://git.php.net/?p=php-src.git;a=commit;h=52e0c4081f8454e9086fc7d1bd1a338ac4e05868
Log: Fix bug #71313 - Use-after-free vulnerability in SPL(SplObjectStorage, unserialize)
 
PHP Copyright © 2001-2017 The PHP Group
All rights reserved.
Last updated: Wed Feb 22 15:01:37 2017 UTC