Bug 1868109 (CVE-2020-7068)

Summary: CVE-2020-7068 php: Use of freed hash key in the phar_parse_zipfile function
Product: [Other] Security Response Reporter: Michael Kaplan <mkaplan>
Component: vulnerabilityAssignee: Red Hat Product Security <security-response-team>
Status: NEW --- QA Contact:
Severity: low Docs Contact:
Priority: low    
Version: unspecifiedCC: fedora, hhorak, jlyle, jorton, rcollet, webstack-team
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: php 7.2.33, php 7.3.21, php 7.4.9 Doc Type: If docs needed, set a value
Doc Text:
Story Points: ---
Clone Of: Environment:
Last Closed: Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Bug Depends On: 1868110, 1869773, 1869774, 1869775, 1869776, 1869777, 1869796    
Bug Blocks: 1868111    

Description Michael Kaplan 2020-08-11 17:30:40 UTC
he phar_parse_zipfile function had use-after-free vulnerability because of mishandling of the actual_alias variable.

----- ext/phar/zip.c -----
int phar_parse_zipfile(php_stream *fp, char *fname, size_t fname_len, char *alias, size_t alias_len, phar_archive_data** pphar, char **error) /* {{{ */
{
	...

	mydata->alias = entry.is_persistent ? pestrndup(actual_alias, mydata->alias_len, 1) : actual_alias;

	if (entry.is_persistent) {
		efree(actual_alias);
	}

	zend_hash_str_add_ptr(&(PHAR_G(phar_alias_map)), actual_alias, mydata->alias_len, mydata);

	...
---------------------------

`actual_alias` variable is allocated by estrndup function, which string is part of data of the zip file.

The above code snippet `mydata->alias` is assigned by `pestrndup(actual_alias, mydata->alias_len, 1)` if entry.is_persistent is true. Or `mydata->alias` is assigned by `actual_alias` variable.
And if `entry.is_persistent` is true, `actual_alias` variable is freed by invoke efree function. `actual_alias` variable is used invoke of zend_hash_str_add_ptr function as 2nd argument.

Problem is that `actual_alias` variable is freed if `entry.is_persistent` is true, the key of `phar_alias_map` will use freed memory. `entry.is_persistent` is true if `phar.cache_list` fields is defined in php.ini file. 

So if `phar.cache_list` is defined with target phar path so that `entry.is_persistent` is true, then it can be that `phar_alias_map` hash key would use sensitive freed memory data such as heap addresses that addresses set via linked list after invoke the efree function.


Possibly affected versions: php 7.2.32, php 7.3.20, php 7.4.8

Upstream Reference:

https://bugs.php.net/bug.php?id=79797
https://www.php.net/ChangeLog-7.php

Comment 1 Michael Kaplan 2020-08-11 17:30:55 UTC
Created php tracking bugs for this issue:

Affects: fedora-all [bug 1868110]

Comment 3 Todd Cullum 2020-08-11 20:52:37 UTC
Flaw summary:

The flaw is in phar_parse_zipfile() of ext/phar/zip.c. When processing a PHP archive file (phar), if a persistent entry is used as defined in php.ini, then memory pointed to by the actual_alias pointer is freed. Directly after the free, the actual_alias pointer is passed to zend_hash_str_add_ptr, where it is dereferenced. Prior to the function call, a copy of the memory pointed to by actual_alias is duplicated and assigned to the mydata->alias pointer. The patch simply uses the unfreed mydata->alias pointer as an argument to the zend_hash_add_str() call rather than the freed memory pointed to by actual_alias.

To trigger this flaw, an attacker needs to place a specially crafted file on the server's filesystem and then load it with PHP. The attacker also needs a setting to be present in PHP's configuration file. Due to this, the attack complexity is high as an attacker would need to find other flaws or already have admin access to the server machine to do this.

Comment 10 Todd Cullum 2020-08-18 16:38:54 UTC
in php 5.4 (RHEL7), it's in ext/phar/zip.c ~ line 687:

        mydata->alias = entry.is_persistent ? pestrndup(actual_alias, mydata->alias_len, 1) : actual_alias;

        if (entry.is_persistent) {
            efree(actual_alias);
        }

        zend_hash_add(&(PHAR_GLOBALS->phar_alias_map), actual_alias, mydata->alias_len, (void*)&mydata, sizeof(phar_archive_data*), NULL);

In this ver, the function is called zend_hash_add instead of zend_hash_str_add.