Skip to content

Conversation

@timurib
Copy link
Contributor

@timurib timurib commented Jun 25, 2018

(This PR duplicates the previous one, but targets to the 7.1 branch instead of 5.6)

The memory leak occurs when

  • a ZipArchive is opened with ZipArchive::CREATE|ZipArchive::OVERWRITE flags,
  • it's empty,
  • it's destroyed without explicit close() method call.

The leak does not occur if the zip file already exist.

https://bugs.php.net/bug.php?id=76524

@cmb69
Copy link
Member

cmb69 commented Jun 25, 2018

cc @remicollet

@timurib
Copy link
Contributor Author

timurib commented Jun 25, 2018

Hmm, if I correctly understand the AppVeyor build log, ZipArchive doesn't print the warning message on Windows:

========DIFF========
002- Warning: Unknown: Cannot destroy the zip context: Can't remove file: No such file or directory in Unknown on line 0

It's not directly related with patch, looks like zip_close() just doesn't fail in this case, but better to check it. I don't have Windows right now, so it will take some time…

$filename = __DIR__ . '/nonexistent.zip';

// The error is not reproduced when file already exist:
if (!file_exists($filename)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just ensure the filename doesn't exist?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw., how would it behave if something was added to that non existent archive? Perhaps this case deserves a separate test.

Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just ensure the filename doesn't exist?

Of course, it's not necessary. I thought this is clearer shows the conditions which cause the problem. If it's not obvious, then I'll rather remove this.

Btw., how would it behave if something was added to that non existent archive? Perhaps this case deserves a separate test.

In this case all right: it just doesn't create an empty archives.
If an archive is not exist and we add some files then new zip file will be created.
If an archive already exists and we don't add any files, but use OVERWRITE then it remove the file without warning. I found this is weird, but it works so.
But you right, I'll check the test suite of the extension for coverage this cases.

if (intern->za) {
if (zip_close(intern->za) != 0) {
php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(intern->za));
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

intern->za was not NULL, so it's probably not destroyed at this point. In further, there are also two superfluous NULL assignments.

Thanks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll check it. Thanks!

@timurib
Copy link
Contributor Author

timurib commented Jun 30, 2018

So, now the zip resources freeing is well as in close() method implementation. @weltling was right, it need to explicit zip_discard call after failed zip_close.

Also, tests are depends on OS now, because on Windows it doesn't show the warning message. I tried to debug this, but I'm not very well on Windows development tools. The difference in this case is occurred because this remove() call in Linux returns error code, but the corresponding function in Windows does not. And I don't even know which of them is correct. Finally, it's irrelevant to the memory leak, so I just split the tests.

@krakjoe krakjoe added the Bug label Jul 2, 2018
if (intern->za) {
if (zip_close(intern->za) != 0) {
#if LIBZIP_VERSION_MAJOR == 1 && LIBZIP_VERSION_MINOR == 3 && LIBZIP_VERSION_MICRO == 1
php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(intern->za));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, why this condition is necessary, is there something special with the exact version? Also, the call to php_error_docref is same in both branches.

Thanks.

Copy link
Contributor Author

@timurib timurib Jul 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, why this condition is necessary, is there something special with the exact version?

Yes, in the 1.3.1 a call zip_discard() after faulty zip_close() may cause segfault (because of use after free). For example, on the build with libzip 1.3.1 and without this condition, next code reproduces the failure:

<?php
$zip = new ZipArchive();
$zip->open(__DIR__.'/filename.zip', ZipArchive::CREATE); // regardless OVERWRITE flag
$zip->addFile(__DIR__); // just an error action

In the gdb:

Warning: Unknown: Cannot destroy the zip context: Read error: Is a directory in Unknown on line 0

Program received signal SIGSEGV, Segmentation fault.
zip_source_free (src=0x20000000b) at zip_source_free.c:46
46	    if (src->refcount > 0) {
(gdb) backtrace 
#0  zip_source_free (src=0x20000000b) at zip_source_free.c:46
#1  0x00007ffff7bcbf7d in zip_source_free (src=0xc83960) at zip_source_free.c:68
#2  0x00007ffff7bcbf7d in zip_source_free (src=0xc7f3f0) at zip_source_free.c:68
#3  0x00007ffff7bc4867 in zip_discard (za=0xc7f680) at zip_discard.c:54
#4  0x00000000005d3d93 in php_zip_object_free_storage (object=0x7ffff6c681e8) at /home/timur/dev/c/php-src/ext/zip/php_zip.c:1008\
...

As @remicollet noticed in the comments for bug 75540, only this libzip version is affected (just in case, I checked the 1.3.0 and 1.3.2, they are ok).

Also, the call to php_error_docref is same in both branches.

Yes, they should be different: the first branch shouldn't call zip_strerror (702ef2736). I'm going to fix it in next commit.

@weltling
Copy link
Contributor

weltling commented Jul 5, 2018

The behavior difference in throwing the warning seems to regard to libzip and platform. I guess, it should be ok to workaround the test by using the %A placeholder, to avoid the test duplication.

Thanks.

@timurib
Copy link
Contributor Author

timurib commented Jul 7, 2018

I guess, it should be ok to workaround the test by using the %A placeholder, to avoid the test duplication.

Unfortunatetly, the %A also "hides" Zend MM messages and the test will passed even if leak is actually present.

What about regular expression in the EXPECTF? Something like that:

--EXPECTF--
ok%r((?!memory leaks detected).)*%r

It works, but I am not sure that is looks fine.

@cmb69
Copy link
Member

cmb69 commented Jul 7, 2018

You can use EXPECTREGEX instead of EXPECTF.

@weltling
Copy link
Contributor

Merged as 08f0885. Thanks!

@weltling weltling closed this Jul 10, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants