php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #14443 Double-free causes segfault in domxml
Submitted: 2001-12-11 21:02 UTC Modified: 2002-01-02 13:49 UTC
From: adam at thejenkins dot org Assigned:
Status: Closed Package: DOM XML related
PHP Version: 4.0.6 OS: Linux Redhat 7.1
Private report: No CVE-ID: None
 [2001-12-11 21:02 UTC] adam at thejenkins dot org
The domxml extension frees memory twice when an XPath 
query is used which results in an empty nodelist.  I'm 
providing a PHP script which reproduces the bug, and a 
patch to fix it.  

How to reproduce:

$xml = 'xml fragment';
$doc = xmldoc($xml);
$xh = $doc->xpath_new_context();
$nodes = xpath_eval($xh, 
   "xpath expression which doesn't match anything");

When the xpath expression doesn't match any nodes, then 
the xmlXPathObjectPtr which gets created by the query gets 
freed twice.  Usually a segfault won't occur unless you do 
8 or more non-matching XPath queries, since a single 
double-free won't always cause a segfault.  But if you run 
php with ElectricFence, a malloc debugging library, then a 
single non-matching XPath query will generate the 
double-free error.  

The bug is caused in the php_xpathptr_eval() function in 
php_domxml.c.  It calls xmlXPathEval() and saves the 
result in xpathobjp.  It then inserts xpathobjp into a 
zend list, le_xpathobjectp.  Later in the function, it 
checks whether xpathobjp contains an empty nodeset, and if 
it does, it calls xmlXPathFreeObject(xpathobjp) and 
returns FALSE.  But it didn't remove the xpathobjp pointer 
from the zend list before deleting it, so when PHP exits 
and cleans up the objects, the pointer sitting on the 
le_xpathobjp list gets freed again.  I fixed the bug by 
checking whether the nodeset is empty before it gets put 
on the zend list, and if it is empty, I free it and just 
return FALSE from the function.  

I'm appending my patch to php_domxml.c, a PHP program that 
triggers the bug in php 4.0.6, and also a stack trace 
showing where the crash occurs.

-- patch to php_domxml.c --
*** php-4.0.6-orig/ext/domxml/php_domxml.c	Thu May 24 
08:41:46 2001
--- php-4.0.6/ext/domxml/php_domxml.c	Tue Dec 11 
01:06:08 2001
***************
*** 1681,1686 ****
--- 1681,1690 ----
  	if (!xpathobjp) {
  		RETURN_FALSE;
  	}
+ 	if (xpathobjp->type == XPATH_NODESET && 
!xpathobjp->nodesetval) {
+ 		xmlXPathFreeObject(xpathobjp);
+ 		RETURN_FALSE;
+ 	}
  
  	ret = zend_list_insert(xpathobjp, le_xpathobjectp);
  	zend_list_addref(ret);
-- end patch --

-- STACK TRACE --
Program received signal SIGSEGV, Segmentation fault.
chunk_free (ar_ptr=0x402fea00, p=0x8185180) at 
malloc.c:3180
3180	in malloc.c
(gdb) bt
#0  chunk_free (ar_ptr=0x402fea00, p=0x8185180) at 
malloc.c:3180
#1  0x4024acd4 in __libc_free (mem=0x8185188) at 
malloc.c:3154
#2  0x4039b747 in xmlXPathFreeObject () from 
/usr/lib/libxml2.so.2
#3  0x40019f9d in php_free_xpath_object (rsrc=0x818514c) 
at php_domxml.c:188
#4  0x080c84b3 in list_entry_destructor (ptr=0x818514c) at 
zend_list.c:179
#5  0x080c70fa in zend_hash_apply_deleter (ht=0x8133fd0, 
p=0x8171b5c)
    at zend_hash.c:615
#6  0x080c722a in zend_hash_graceful_destroy 
(ht=0x8133fd0) at zend_hash.c:666
#7  0x080c85e0 in zend_destroy_rsrc_list () at 
zend_list.c:234
#8  0x080bb42d in shutdown_executor () at 
zend_execute_API.c:179
#9  0x080c3265 in zend_deactivate () at zend.c:540
#10 0x0805de9d in php_request_shutdown (dummy=0x0) at 
main.c:660
#11 0x0805cfdd in main (argc=4, argv=0xbffff7f4) at 
cgi_main.c:751
#12 0x401e6627 in __libc_start_main (main=0x805c740 
<main>, argc=4, 
    ubp_av=0xbffff7f4, init=0x805ad5c <_init>, 
fini=0x80f4920 <_fini>, 
    rtld_fini=0x4000dcd4 <_dl_fini>, stack_end=0xbffff7ec)
    at ../sysdeps/generic/libc-start.c:129
-- END STACK TRACE --

-- program to demonstrate bug --
Call this program with the serverid parameter set to 1 or 
2 will work fine, but call it with serverid=3 and it will 
segfault.

<?

$xml = <<<EOT
<config type="kvp" customer="customer0">

<!-- ****************** STREAMING MEDIA SERVERS 
************ -->
  <mediaserverlist>
    <mediaserver id="1" type="windowsmedia">
      <host> cambridge.knumi.com </host>
      <baseurl> mms://cambridge.knumi.com/mysore-customer0 
</baseurl>
      <user> realxfer </user>
      <password> xferreal </password>
      <basedir> mysore-customer0 </basedir>
      <basedeleteddir> deleted-mysore-customer0 
</basedeleteddir>
      <rpcurl> 
http://cambridge.knumi.com/cgi-bin/deletemediafiles.pl 
</rpcurl>
    </mediaserver>

    <mediaserver id="2" type="real">
      <host> colo3.knumi.net </host>
      <baseurl> rtsp://colo3.knumi.net/mysore-customer0 
</baseurl>
      <user> realxfer </user>
      <password> krs@9555 </password>
      <basedir> mysore-customer0 </basedir>
      <basedeleteddir> deleted-mysore-customer0 
</basedeleteddir>
      <rpcurl> 
http://cambridge.knumi.com/cgi-bin/deletemediafiles.pl 
</rpcurl>
    </mediaserver>


  </mediaserverlist>
</config>
EOT;

$serverArray = array();
if (!isset($serverid)) {
  $serverid = 2;
}

$doc = xmldoc($xml);
$xp = $doc->xpath_new_context();

readServer($xp, "media", $serverid, "real");
print_r($serverArray);

function readServer($xp, $sType, $sId, $sSubType) 
{
  global $serverArray;

  $xPath = "/config/" . $sType . "serverlist/" . $sType . 
     "server[@type='$sSubType' and @id='$sId']/";

  $serverArray[$sType][$sId][$sSubType]["host"] = 
     getNodeValue($xp, $xPath . "host");
  $serverArray[$sType][$sId][$sSubType]["port"] = 
     getNodeValue($xp, $xPath . "port");
  $serverArray[$sType][$sId][$sSubType]["baseurl"] = 
     getNodeValue($xp, $xPath . "baseurl");
  $serverArray[$sType][$sId][$sSubType]["name"] = 
     getNodeValue($xp, $xPath . "name");
  $serverArray[$sType][$sId][$sSubType]["user"] = 
     getNodeValue($xp, $xPath . "user");
  $serverArray[$sType][$sId][$sSubType]["password"] = 
     getNodeValue($xp, $xPath . "password");
  $serverArray[$sType][$sId][$sSubType]["indexdir"] = 
     getNodeValue($xp, $xPath . "indexdir");
  $serverArray[$sType][$sId][$sSubType]["basedir"] = 
     getNodeValue($xp, $xPath . "basedir");
  $serverArray[$sType][$sId][$sSubType]["basedeleteddir"] 
= 
     getNodeValue($xp, $xPath . "basedeleteddir");
  $serverArray[$sType][$sId][$sSubType]["rpcurl"] = 
     getNodeValue($xp, $xPath . "rpcurl");
}

function &getNode($xpath_context, $xpath_expression) 
{
  if (! $xpath_expression) {
    return false;
  }
  $nodes = xpath_eval($xpath_context, $xpath_expression);

  if (!$nodes || count($nodes->nodeset) == 0) {
    return false;
  } else {
    return $nodes->nodeset[0];
  }
}

function getNodeValue($xpath_context, $xpath_expression) {
	$node = getNode($xpath_context, $xpath_expression);
	return $node ? trim($node->content) : false;
}

?>
--- end test program --

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2001-12-11 21:48 UTC] mfischer@php.net
The code has changed quite a lot since 4.0.6. I can't reproduce the crash with the current CVS version. Please give it yourself a try.

Feedback.

PS: Patches not against CVS are most of the time useless because code tends to change a lot.
 [2001-12-12 02:04 UTC] adam at thejenkins dot org
I just looked at the php4 code in the CVS tree and it 
appears that this bug is already fixed, in a better way 
than I did it.  We'll have to look into upgrading to 4.1 I 
guess.  Thank you.

Adam

 [2001-12-12 02:18 UTC] mfischer@php.net
Ok, just don't forget to give feedback. The quite has changed but doesn't necessarily mean the bug has been fixed ;-)

Feedback.
 [2001-12-12 02:19 UTC] mfischer@php.net
'The code has changed quite a lot ...' ...
 [2002-01-02 13:49 UTC] lobbin@php.net
No feedback. Closing.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Mar 29 08:01:27 2024 UTC