Skip to content

Commit 291252d

Browse files
committed
Fix bzopen() ownership of the stream it wraps
bzopen() with a stream argument, and the wrapper fallback in _php_stream_bz2open(), pass the stream's own descriptor to BZ2_bzdopen(). bzlib takes ownership of it and closes it in BZ2_bzclose(), after which the inner stream close closes the same descriptor number again. The bz2 stream also keeps a raw pointer to the inner stream while holding a reference on its resource only, so closing the inner stream with fclose() left a dangling pointer that the bz2 close dereferenced, while bzlib kept using a descriptor number that could already belong to another file. Hand bzlib a duplicate of the descriptor and resolve the inner stream from its resource on close, so a closed inner stream is skipped. Closes GH-23824
1 parent bba11e6 commit 291252d

3 files changed

Lines changed: 79 additions & 6 deletions

File tree

‎NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ PHP NEWS
66
. Fixed BcMath\Number results that truncate to zero keeping a negative sign
77
and comparing less than zero. (Ilia Alshanetsky)
88

9+
- BZ2:
10+
. Fixed double close of the descriptor and use-after-free of the inner
11+
stream when bzopen() is given a stream resource. (Jakub Zelenka)
12+
913
- CLI
1014
. Fix GH-22567 (Windows ZTS CLI SAPI should refresh its TSRMLS cache during
1115
request activation). (matyhtf)

‎ext/bz2/bz2.c‎

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ ZEND_GET_MODULE(bz2)
5959

6060
struct php_bz2_stream_data_t {
6161
BZFILE *bz_file;
62-
php_stream *stream;
62+
zend_resource *stream_res;
6363
};
6464

6565
/* {{{ BZip2 stream implementation */
@@ -131,8 +131,15 @@ static int php_bz2iop_close(php_stream *stream, int close_handle)
131131
BZ2_bzclose(self->bz_file);
132132
}
133133

134-
if (self->stream) {
135-
php_stream_free(self->stream, PHP_STREAM_FREE_CLOSE | (close_handle == 0 ? PHP_STREAM_FREE_PRESERVE_HANDLE : 0));
134+
/* The inner stream may have been closed by the user, only its resource is held */
135+
if (self->stream_res) {
136+
php_stream *inner = zend_fetch_resource2(
137+
self->stream_res, NULL, php_file_le_stream(), php_file_le_pstream());
138+
if (inner) {
139+
php_stream_free(inner, PHP_STREAM_FREE_CLOSE | (close_handle == 0 ? PHP_STREAM_FREE_PRESERVE_HANDLE : 0));
140+
} else {
141+
zend_list_delete(self->stream_res);
142+
}
136143
}
137144

138145
efree(self);
@@ -158,15 +165,33 @@ const php_stream_ops php_stream_bz2io_ops = {
158165
};
159166

160167
/* {{{ Bzip2 stream openers */
168+
169+
/* bzlib closes the descriptor it is given, so it gets a duplicate */
170+
static BZFILE *php_bz2_bzdopen(php_socket_t fd, const char *mode)
171+
{
172+
int dup_fd = dup((int) fd);
173+
if (dup_fd == -1) {
174+
return NULL;
175+
}
176+
177+
BZFILE *bz = BZ2_bzdopen(dup_fd, mode);
178+
if (!bz) {
179+
close(dup_fd);
180+
}
181+
182+
return bz;
183+
}
184+
161185
PHP_BZ2_API php_stream *_php_stream_bz2open_from_BZFILE(BZFILE *bz,
162186
const char *mode, php_stream *innerstream STREAMS_DC)
163187
{
164188
struct php_bz2_stream_data_t *self;
165189

166190
self = emalloc(sizeof(*self));
167191

168-
self->stream = innerstream;
192+
self->stream_res = NULL;
169193
if (innerstream) {
194+
self->stream_res = innerstream->res;
170195
GC_ADDREF(innerstream->res);
171196
}
172197
self->bz_file = bz;
@@ -223,7 +248,7 @@ PHP_BZ2_API php_stream *_php_stream_bz2open(php_stream_wrapper *wrapper,
223248
if (stream) {
224249
php_socket_t fd;
225250
if (SUCCESS == php_stream_cast(stream, PHP_STREAM_AS_FD, (void **) &fd, REPORT_ERRORS)) {
226-
bz_file = BZ2_bzdopen((int)fd, mode);
251+
bz_file = php_bz2_bzdopen(fd, mode);
227252
}
228253
}
229254

@@ -399,7 +424,10 @@ PHP_FUNCTION(bzopen)
399424
RETURN_FALSE;
400425
}
401426

402-
bz = BZ2_bzdopen((int)fd, mode);
427+
bz = php_bz2_bzdopen(fd, mode);
428+
if (!bz) {
429+
RETURN_FALSE;
430+
}
403431

404432
stream = php_stream_bz2open_from_BZFILE(bz, mode, stream);
405433
} else {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
bzopen() on a stream resource: the inner stream may be closed first
3+
--EXTENSIONS--
4+
bz2
5+
--FILE--
6+
<?php
7+
$file = tempnam(sys_get_temp_dir(), 'bz2');
8+
$victim = tempnam(sys_get_temp_dir(), 'bz2');
9+
$payload = str_repeat("payload", 100);
10+
11+
$fp = fopen($file, 'w');
12+
$bz = bzopen($fp, 'w');
13+
var_dump(bzwrite($bz, $payload));
14+
15+
// The bz2 stream owns its own descriptor, so closing the inner stream neither
16+
// invalidates it nor makes it write into the next descriptor opened
17+
var_dump(fclose($fp));
18+
$other = fopen($victim, 'w');
19+
bzclose($bz);
20+
var_dump(fclose($other));
21+
22+
var_dump(bzdecompress(file_get_contents($file)) === $payload);
23+
var_dump(filesize($victim));
24+
25+
$fp = fopen($file, 'r');
26+
$bz = bzopen($fp, 'r');
27+
var_dump(fclose($fp));
28+
var_dump(bzread($bz, 8192) === $payload);
29+
bzclose($bz);
30+
31+
unlink($file);
32+
unlink($victim);
33+
?>
34+
--EXPECT--
35+
int(700)
36+
bool(true)
37+
bool(true)
38+
bool(true)
39+
int(0)
40+
bool(true)
41+
bool(true)

0 commit comments

Comments
 (0)