Skip to content

Commit abf95a0

Browse files
cataphractclaude
andcommitted
Fix use-after-free when replacing password callback with itself
If the old and new callable are the same zval with refcount 1, zval_ptr_dtor would free it before Z_TRY_ADDREF could save it. Addref the incoming fci first, then dtor the old one. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6093995 commit abf95a0

1 file changed

Lines changed: 74 additions & 0 deletions

File tree

libarchive.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ static zend_object *arch_ce_create_object(zend_class_entry *ce)
181181
zobj->arch_disk = NULL;
182182
zobj->write_disk_options = 0;
183183
zobj->entry_generation = 0;
184+
memset(&zobj->password_fci, 0, sizeof(zobj->password_fci));
185+
memset(&zobj->password_fcc, 0, sizeof(zobj->password_fcc));
186+
zobj->password_cb_last_result = NULL;
184187
zend_object_std_init(&zobj->parent, ce);
185188
zobj->parent.handlers = &arch_oh;
186189

@@ -219,6 +222,14 @@ static void arch_oh_free_obj(zend_object *zobj)
219222
(void)archive_write_free(obj->arch_disk);
220223
obj->arch_disk = NULL;
221224
}
225+
if (obj->password_fci.size != 0) {
226+
zval_ptr_dtor(&obj->password_fci.function_name);
227+
obj->password_fci.size = 0;
228+
}
229+
if (obj->password_cb_last_result) {
230+
zend_string_release(obj->password_cb_last_result);
231+
obj->password_cb_last_result = NULL;
232+
}
222233
zend_object_std_dtor(zobj);
223234
}
224235

@@ -239,6 +250,36 @@ PHP_METHOD(libarchive_Archive, __construct)
239250

240251
static bool arch_obj_open_read_stream(arch_object *arch_obj);
241252

253+
static const char *arch_passphrase_callback(struct archive *a, void *_client_data)
254+
{
255+
(void)a;
256+
arch_object *arch_obj = (arch_object *)_client_data;
257+
258+
zval retval;
259+
ZVAL_UNDEF(&retval);
260+
arch_obj->password_fci.retval = &retval;
261+
arch_obj->password_fci.param_count = 0;
262+
arch_obj->password_fci.params = NULL;
263+
264+
if (zend_call_function(&arch_obj->password_fci, &arch_obj->password_fcc) == FAILURE
265+
|| Z_TYPE(retval) == IS_UNDEF) {
266+
return NULL;
267+
}
268+
269+
if (arch_obj->password_cb_last_result) {
270+
zend_string_release(arch_obj->password_cb_last_result);
271+
arch_obj->password_cb_last_result = NULL;
272+
}
273+
274+
if (Z_TYPE(retval) == IS_NULL) {
275+
return NULL;
276+
}
277+
278+
arch_obj->password_cb_last_result = zval_get_string(&retval);
279+
zval_ptr_dtor(&retval);
280+
return ZSTR_VAL(arch_obj->password_cb_last_result);
281+
}
282+
242283
static void arch_obj_setup_support(arch_object *arch_obj)
243284
{
244285
if (arch_obj->filters == NULL) {
@@ -276,6 +317,10 @@ static bool arch_obj_open_read(arch_object *arch_obj)
276317
php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&fd, 0) == SUCCESS) {
277318
arch_obj->archive = archive_read_new();
278319
arch_obj_setup_support(arch_obj);
320+
if (arch_obj->password_fci.size != 0) {
321+
archive_read_set_passphrase_callback(arch_obj->archive, arch_obj,
322+
arch_passphrase_callback);
323+
}
279324
int res = archive_read_open_fd(arch_obj->archive, fd, 10240);
280325
if (res != ARCHIVE_OK) {
281326
zend_throw_exception_ex(
@@ -315,6 +360,10 @@ static bool arch_obj_open_read_stream(arch_object *arch_obj)
315360

316361
arch_obj->archive = archive_read_new();
317362
arch_obj_setup_support(arch_obj);
363+
if (arch_obj->password_fci.size != 0) {
364+
archive_read_set_passphrase_callback(arch_obj->archive, arch_obj,
365+
arch_passphrase_callback);
366+
}
318367
res = archive_read_open_FILE(arch_obj->archive, fp);
319368
if (res != ARCHIVE_OK) {
320369
zend_throw_exception_ex(
@@ -343,6 +392,31 @@ PHP_METHOD(libarchive_Archive, fromStream)
343392
arch_obj->write_disk_options = (int)flags;
344393
}
345394

395+
PHP_METHOD(libarchive_Archive, withPasswordCallback)
396+
{
397+
zend_fcall_info fci;
398+
zend_fcall_info_cache fcc;
399+
ZEND_PARSE_PARAMETERS_START(1, 1)
400+
Z_PARAM_FUNC(fci, fcc)
401+
ZEND_PARSE_PARAMETERS_END();
402+
403+
arch_object *arch_obj = arch_object_from_zv(getThis());
404+
if (arch_obj->archive != NULL) {
405+
zend_throw_exception(except_ce,
406+
"Cannot set password callback after archive has been opened", -1);
407+
return;
408+
}
409+
410+
Z_TRY_ADDREF(fci.function_name);
411+
if (arch_obj->password_fci.size != 0) {
412+
zval_ptr_dtor(&arch_obj->password_fci.function_name);
413+
}
414+
arch_obj->password_fci = fci;
415+
arch_obj->password_fcc = fcc;
416+
417+
RETURN_THIS();
418+
}
419+
346420
PHP_METHOD(libarchive_Archive, supportFormats)
347421
{
348422
zval *args;

0 commit comments

Comments
 (0)