Skip to content

Commit 39e4f0e

Browse files
committed
feedback then now we have to exercise the two cases separately.
1 parent 1ea12af commit 39e4f0e

4 files changed

Lines changed: 62 additions & 38 deletions

File tree

ext/zip/php_zip.c

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,12 @@ static bool php_zipobj_close(ze_zip_object *obj) /* {{{ */
607607
}
608608

609609
obj->za = NULL;
610+
611+
if (obj->bailout_callback) {
612+
obj->bailout_callback = false;
613+
zend_bailout();
614+
}
615+
610616
return success;
611617
}
612618
/* }}} */
@@ -1049,10 +1055,16 @@ static void php_zip_object_dtor(zend_object *object)
10491055

10501056
if (intern->za) {
10511057
if (zip_close(intern->za) != 0) {
1052-
php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(intern->za));
1058+
if (!intern->bailout_callback) {
1059+
php_error_docref(NULL, E_WARNING, "Cannot destroy the zip context: %s", zip_strerror(intern->za));
1060+
}
10531061
zip_discard(intern->za);
10541062
}
10551063
intern->za = NULL;
1064+
if (intern->bailout_callback) {
1065+
intern->bailout_callback = false;
1066+
zend_bailout();
1067+
}
10561068
}
10571069
}
10581070

@@ -2902,17 +2914,20 @@ PHP_METHOD(ZipArchive, getStream)
29022914
#ifdef HAVE_PROGRESS_CALLBACK
29032915
static void php_zip_progress_callback(zip_t *arch, double state, void *ptr)
29042916
{
2905-
if (!EG(active)) {
2917+
ze_zip_object *obj = ptr;
2918+
2919+
if (!EG(active) || obj->bailout_callback) {
29062920
return;
29072921
}
29082922

29092923
zval cb_args[1];
2910-
ze_zip_object *obj = ptr;
29112924

29122925
ZVAL_DOUBLE(&cb_args[0], state);
29132926

29142927
zend_try {
29152928
zend_call_known_fcc(&obj->progress_callback, NULL, 1, cb_args, NULL);
2929+
} zend_catch {
2930+
obj->bailout_callback = true;
29162931
} zend_end_try();
29172932
}
29182933

@@ -2956,13 +2971,14 @@ static int php_zip_cancel_callback(zip_t *arch, void *ptr)
29562971
zval cb_retval;
29572972
ze_zip_object *obj = ptr;
29582973

2959-
if (!EG(active)) {
2974+
if (!EG(active) || obj->bailout_callback) {
29602975
return 0;
29612976
}
29622977

29632978
zend_try {
29642979
zend_call_known_fcc(&obj->cancel_callback, &cb_retval, 0, NULL, NULL);
29652980
} zend_catch {
2981+
obj->bailout_callback = true;
29662982
/* Cancel if a bailout occurs to allow cleanup to happen */
29672983
return -1;
29682984
} zend_end_try();

ext/zip/php_zip.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ typedef struct _ze_zip_object {
7272
zip_int64_t last_id;
7373
int err_zip;
7474
int err_sys;
75+
bool bailout_callback;
7576
#ifdef HAVE_PROGRESS_CALLBACK
7677
zend_fcall_info_cache progress_callback;
7778
#endif

ext/zip/tests/gh22176.phpt

Lines changed: 8 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,33 @@
11
--TEST--
2-
GH-22176 (Memory leak when a ZipArchive cancel/progress callback bails out in the shutdown destructor)
2+
GH-22176 (Memory leak when a ZipArchive cancel callback bails out in the shutdown destructor)
33
--EXTENSIONS--
44
zip
55
--SKIPIF--
66
<?php
77
if (!method_exists('ZipArchive', 'registerCancelCallback')) die('skip libzip too old');
8-
if (!method_exists('ZipArchive', 'registerProgressCallback')) die('skip libzip too old');
98
?>
109
--FILE--
1110
<?php
12-
$dir = __DIR__ . '/';
13-
14-
$cancel = new ZipArchive;
15-
$cancel->open($dir . 'gh22176_cancel.zip', ZipArchive::CREATE);
16-
$cancel->registerCancelCallback(function () {
11+
$zip = new ZipArchive;
12+
$zip->open(__DIR__ . '/gh22176_cancel.zip', ZipArchive::CREATE);
13+
$zip->registerCancelCallback(function () {
1714
throw new \Exception('cancel boom');
1815
});
19-
$cancel->addFromString('test', 'test');
20-
21-
$progress = new ZipArchive;
22-
$progress->open($dir . 'gh22176_progress.zip', ZipArchive::CREATE);
23-
$progress->registerProgressCallback(0.5, function ($r) {
24-
throw new \Exception('progress boom');
25-
});
26-
$progress->addFromString('test', 'test');
27-
16+
$zip->addFromString('test', 'test');
2817
echo "done\n";
29-
// Both archives are flushed and the objects destroyed during request
30-
// shutdown; the thrown exceptions bail out through libzip's zip_close()
31-
// without leaking its internal state.
18+
// The archive is flushed and the object destroyed during request shutdown;
19+
// the thrown exception bails out through libzip's zip_close() without leaking
20+
// its internal state, and the bailout resumes once libzip has unwound.
3221
?>
3322
--CLEAN--
3423
<?php
3524
@unlink(__DIR__ . '/gh22176_cancel.zip');
36-
@unlink(__DIR__ . '/gh22176_progress.zip');
3725
?>
3826
--EXPECTF--
3927
done
4028

41-
Fatal error: Uncaught Exception: progress boom in %s:%d
42-
Stack trace:
43-
#0 [internal function]: {closure:%s:%d}(0.0)
44-
#1 {main}
45-
thrown in %s on line %d
46-
47-
Fatal error: Uncaught Exception: progress boom in %s:%d
48-
Stack trace:
49-
#0 [internal function]: {closure:%s:%d}(1.0)
50-
#1 {main}
51-
thrown in %s on line %d
52-
5329
Fatal error: Uncaught Exception: cancel boom in %s:%d
5430
Stack trace:
5531
#0 [internal function]: {closure:%s:%d}()
5632
#1 {main}
5733
thrown in %s on line %d
58-
59-
Warning: PHP Request Shutdown: Cannot destroy the zip context: %s in Unknown on line 0
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
GH-22176 (Memory leak when a ZipArchive progress callback bails out in the shutdown destructor)
3+
--EXTENSIONS--
4+
zip
5+
--SKIPIF--
6+
<?php
7+
if (!method_exists('ZipArchive', 'registerProgressCallback')) die('skip libzip too old');
8+
?>
9+
--FILE--
10+
<?php
11+
$zip = new ZipArchive;
12+
$zip->open(__DIR__ . '/gh22176_progress.zip', ZipArchive::CREATE);
13+
$zip->registerProgressCallback(0.5, function ($r) {
14+
throw new \Exception('progress boom');
15+
});
16+
$zip->addFromString('test', 'test');
17+
echo "done\n";
18+
// The archive is flushed and the object destroyed during request shutdown;
19+
// the thrown exception bails out through libzip's zip_close() without leaking
20+
// its internal state, and the bailout resumes once libzip has unwound.
21+
?>
22+
--CLEAN--
23+
<?php
24+
@unlink(__DIR__ . '/gh22176_progress.zip');
25+
?>
26+
--EXPECTF--
27+
done
28+
29+
Fatal error: Uncaught Exception: progress boom in %s:%d
30+
Stack trace:
31+
#0 [internal function]: {closure:%s:%d}(0.0)
32+
#1 {main}
33+
thrown in %s on line %d

0 commit comments

Comments
 (0)