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 (profile)
Status: Closed Package: SPL related
PHP Version: 7.* OS: Ubuntu 15.10
Private report: No CVE-ID: None
Welcome back! If you're the original bug submitter, here's where you can edit the bug or add additional notes.
If you forgot your password, you can retrieve your password here.
Password:
Status:
Package:
Bug Type:
Summary:
From: sean dot heelan at gmail dot com
New email:
PHP Version: OS:

 

 [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

Pull Requests

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-2024 The PHP Group
All rights reserved.
Last updated: Thu Nov 21 12:01:29 2024 UTC