Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ PHP NEWS
. Fixed a leak in Locale::getKeywords() when a keyword value cannot be
read. (iliaal)

- Phar:
. Fixed bug GH-23418 (Use-after-free when looking up mounted directories).
(Weilin Du)


27 Aug 2026, PHP 8.6.0beta2

Expand Down
28 changes: 24 additions & 4 deletions ext/gd/gd.c
Original file line number Diff line number Diff line change
Expand Up @@ -4440,21 +4440,39 @@ static void _php_image_output_ctxfree(struct gdIOCtx *ctx) /* {{{ */
efree(ctx);
} /* }}} */

typedef struct {
gdIOCtx ctx;
size_t buf_len;
unsigned char buf[8192];
} php_gd_stream_ctx;

static void _php_image_stream_flush(php_gd_stream_ctx *stream_ctx) /* {{{ */
{
if (stream_ctx->buf_len) {
php_stream_write((php_stream *) stream_ctx->ctx.data, (char *) stream_ctx->buf, stream_ctx->buf_len);
stream_ctx->buf_len = 0;
}
} /* }}} */

static void _php_image_stream_putc(struct gdIOCtx *ctx, int c) /* {{{ */ {
char ch = (char) c;
php_stream * stream = (php_stream *)ctx->data;
php_stream_write(stream, &ch, 1);
php_gd_stream_ctx *stream_ctx = (php_gd_stream_ctx *) ctx;
if (stream_ctx->buf_len == sizeof(stream_ctx->buf)) {
_php_image_stream_flush(stream_ctx);
}
stream_ctx->buf[stream_ctx->buf_len++] = (unsigned char) c;
} /* }}} */

static int _php_image_stream_putbuf(struct gdIOCtx *ctx, const void* buf, int l) /* {{{ */
{
php_stream * stream = (php_stream *)ctx->data;
_php_image_stream_flush((php_gd_stream_ctx *) ctx);
return php_stream_write(stream, (void *)buf, l);
} /* }}} */

static void _php_image_stream_ctxfree(struct gdIOCtx *ctx) /* {{{ */
{
if(ctx->data) {
_php_image_stream_flush((php_gd_stream_ctx *) ctx);
ctx->data = NULL;
}
efree(ctx);
Expand All @@ -4463,14 +4481,16 @@ static void _php_image_stream_ctxfree(struct gdIOCtx *ctx) /* {{{ */
static void _php_image_stream_ctxfreeandclose(struct gdIOCtx *ctx) /* {{{ */
{
if(ctx->data) {
_php_image_stream_flush((php_gd_stream_ctx *) ctx);
php_stream_close((php_stream *) ctx->data);
ctx->data = NULL;
}
efree(ctx);
} /* }}} */

static gdIOCtx *create_stream_context(php_stream *stream, int close_stream) {
gdIOCtx *ctx = ecalloc(1, sizeof(gdIOCtx));
php_gd_stream_ctx *stream_ctx = ecalloc(1, sizeof(php_gd_stream_ctx));
gdIOCtx *ctx = &stream_ctx->ctx;

ctx->putC = _php_image_stream_putc;
ctx->putBuf = _php_image_stream_putbuf;
Expand Down
37 changes: 37 additions & 0 deletions ext/gd/tests/gh23457.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
GH-23457 (imagebmp() writes to the stream one byte at a time)
--EXTENSIONS--
gd
--FILE--
<?php
class write_counter
{
public $context;

public static int $writes = 0;

public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
{
return true;
}

public function stream_write(string $data): int
{
self::$writes++;
return strlen($data);
}

public function stream_close(): void
{
}
}

stream_wrapper_register('gh23457', write_counter::class);

$im = imagecreatetruecolor(200, 200);
var_dump(imagebmp($im, 'gh23457://image.bmp'));
var_dump(write_counter::$writes < 100);
?>
--EXPECT--
bool(true)
bool(true)
32 changes: 32 additions & 0 deletions ext/phar/tests/gh23418.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
GH-23418: Access a subdirectory of a mounted directory with a trailing slash
--EXTENSIONS--
phar
--INI--
phar.readonly=0
--FILE--
<?php
$phar = __DIR__ . '/gh23418.phar';
$mount = __DIR__ . '/gh23418';

@mkdir($mount . '/s2', 0777, true);

$p = new Phar($phar);
$p->addFromString('x.txt', 'x');
$p->setStub('<?php __HALT_COMPILER(); ?>');
unset($p);

$p = new Phar($phar);
Phar::mount('phar://' . $phar . '/m', $mount);
$info = $p['m/s2/'];

echo get_class($info), ', isDir=', $info->isDir() ? 'true' : 'false', PHP_EOL;
?>
--CLEAN--
<?php
@unlink(__DIR__ . '/gh23418.phar');
@rmdir(__DIR__ . '/gh23418/s2');
@rmdir(__DIR__ . '/gh23418');
?>
--EXPECT--
PharFileInfo, isDir=true
13 changes: 8 additions & 5 deletions ext/phar/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -1272,7 +1272,7 @@ phar_entry_info *phar_get_entry_info_dir(phar_archive_data *phar, char *path, si
if (ZSTR_LEN(str_key) >= path_len || strncmp(ZSTR_VAL(str_key), path, ZSTR_LEN(str_key))) {
continue;
} else {
char *test;
char *test, *mount_path;
size_t test_len;
php_stream_statbuf ssb;

Expand Down Expand Up @@ -1316,23 +1316,26 @@ phar_entry_info *phar_get_entry_info_dir(phar_archive_data *phar, char *path, si
}

/* mount the file just in time */
if (SUCCESS != phar_mount_entry(phar, test, test_len, path, path_len)) {
efree(test);
mount_path = estrndup(path, path_len);
if (SUCCESS != phar_mount_entry(phar, test, test_len, mount_path, path_len)) {
if (error) {
spprintf(error, 4096, "phar error: path \"%s\" exists as file \"%s\" and could not be mounted", path, test);
}
efree(mount_path);
efree(test);
return NULL;
}

efree(test);
efree(mount_path);

entry = zend_hash_str_find_ptr(&phar->manifest, path, path_len);
if (!entry) {
if (error) {
spprintf(error, 4096, "phar error: path \"%s\" exists as file \"%s\" and could not be retrieved after being mounted", path, test);
}
efree(test);
return NULL;
}
efree(test);
return entry;
}
} ZEND_HASH_FOREACH_END();
Expand Down
85 changes: 73 additions & 12 deletions sapi/fpm/fpm/fpm_unix.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#include "fpm_config.h"

#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <string.h>
#include <sys/time.h>
#include <sys/resource.h>
Expand Down Expand Up @@ -53,6 +56,42 @@ static inline bool fpm_unix_is_id(const char* name)
return strlen(name) == strspn(name, "0123456789");
}

static bool fpm_unix_parse_uid(struct fpm_worker_pool_s *wp, const char *name, uid_t *uid)
{
uintmax_t sentinel = (uintmax_t) ((uid_t) -1);
uintmax_t max = (uid_t) -1 > (uid_t) 0
? (uintmax_t) ((uid_t) -1)
: (UINTMAX_C(1) << (sizeof(uid_t) * CHAR_BIT - 1)) - 1;

errno = 0;
uintmax_t value = strtoumax(name, NULL, 10);
if (errno == ERANGE || value > max || value == sentinel) {
zlog(ZLOG_ERROR, "[pool %s] user ID '%s' is out of range", wp->config->name, name);
return false;
}

*uid = (uid_t) value;
return true;
}

static bool fpm_unix_parse_gid(struct fpm_worker_pool_s *wp, const char *name, gid_t *gid)
{
uintmax_t sentinel = (uintmax_t) ((gid_t) -1);
uintmax_t max = (gid_t) -1 > (gid_t) 0
? (uintmax_t) ((gid_t) -1)
: (UINTMAX_C(1) << (sizeof(gid_t) * CHAR_BIT - 1)) - 1;

errno = 0;
uintmax_t value = strtoumax(name, NULL, 10);
if (errno == ERANGE || value > max || value == sentinel) {
zlog(ZLOG_ERROR, "[pool %s] group ID '%s' is out of range", wp->config->name, name);
return false;
}

*gid = (gid_t) value;
return true;
}

static struct passwd *fpm_unix_get_passwd(struct fpm_worker_pool_s *wp, const char *name, int flags)
{
struct passwd *pwd = getpwnam(name);
Expand Down Expand Up @@ -93,7 +132,14 @@ static inline bool fpm_unix_check_listen_address(struct fpm_worker_pool_s *wp, c

static inline bool fpm_unix_check_passwd(struct fpm_worker_pool_s *wp, const char *name, int flags)
{
return !name || fpm_unix_is_id(name) || fpm_unix_get_passwd(wp, name, flags);
if (!name || !*name) {
return true;
}
if (fpm_unix_is_id(name)) {
uid_t uid;
return fpm_unix_parse_uid(wp, name, &uid);
}
return fpm_unix_get_passwd(wp, name, flags) != NULL;
}

static struct group *fpm_unix_get_group(struct fpm_worker_pool_s *wp, const char *name, int flags)
Expand All @@ -109,7 +155,14 @@ static struct group *fpm_unix_get_group(struct fpm_worker_pool_s *wp, const char

static inline bool fpm_unix_check_group(struct fpm_worker_pool_s *wp, const char *name, int flags)
{
return !name || fpm_unix_is_id(name) || fpm_unix_get_group(wp, name, flags);
if (!name || !*name) {
return true;
}
if (fpm_unix_is_id(name)) {
gid_t gid;
return fpm_unix_parse_gid(wp, name, &gid);
}
return fpm_unix_get_group(wp, name, flags) != NULL;
}

bool fpm_unix_test_config(struct fpm_worker_pool_s *wp)
Expand All @@ -133,8 +186,8 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */
/* uninitialized */
wp->socket_acl = NULL;
#endif
wp->socket_uid = -1;
wp->socket_gid = -1;
wp->socket_uid = (uid_t) -1;
wp->socket_gid = (gid_t) -1;
wp->socket_mode = 0660;

if (!c) {
Expand Down Expand Up @@ -252,7 +305,9 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */

if (c->listen_owner && *c->listen_owner) {
if (fpm_unix_is_id(c->listen_owner)) {
wp->socket_uid = strtoul(c->listen_owner, 0, 10);
if (!fpm_unix_parse_uid(wp, c->listen_owner, &wp->socket_uid)) {
return -1;
}
} else {
struct passwd *pwd;

Expand All @@ -268,7 +323,9 @@ int fpm_unix_resolve_socket_permissions(struct fpm_worker_pool_s *wp) /* {{{ */

if (c->listen_group && *c->listen_group) {
if (fpm_unix_is_id(c->listen_group)) {
wp->socket_gid = strtoul(c->listen_group, 0, 10);
if (!fpm_unix_parse_gid(wp, c->listen_group, &wp->socket_gid)) {
return -1;
}
} else {
struct group *grp;

Expand Down Expand Up @@ -325,7 +382,7 @@ int fpm_unix_set_socket_permissions(struct fpm_worker_pool_s *wp, const char *pa
/* When listen.users and listen.groups not configured, continue with standard right */
#endif

if (wp->socket_uid != -1 || wp->socket_gid != -1) {
if (wp->socket_uid != (uid_t) -1 || wp->socket_gid != (gid_t) -1) {
if (0 > chown(path, wp->socket_uid, wp->socket_gid)) {
zlog(ZLOG_SYSERROR, "[pool %s] failed to chown() the socket '%s'", wp->config->name, wp->config->listen_address);
return -1;
Expand Down Expand Up @@ -354,7 +411,9 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */
if (is_root) {
if (wp->config->user && *wp->config->user) {
if (fpm_unix_is_id(wp->config->user)) {
wp->set_uid = strtoul(wp->config->user, 0, 10);
if (!fpm_unix_parse_uid(wp, wp->config->user, &wp->set_uid)) {
return -1;
}
pwd = getpwuid(wp->set_uid);
if (pwd) {
wp->set_gid = pwd->pw_gid;
Expand All @@ -378,7 +437,9 @@ static int fpm_unix_conf_wp(struct fpm_worker_pool_s *wp) /* {{{ */

if (wp->config->group && *wp->config->group) {
if (fpm_unix_is_id(wp->config->group)) {
wp->set_gid = strtoul(wp->config->group, 0, 10);
if (!fpm_unix_parse_gid(wp, wp->config->group, &wp->set_gid)) {
return -1;
}
} else {
struct group *grp;

Expand Down Expand Up @@ -476,17 +537,17 @@ int fpm_unix_init_child(struct fpm_worker_pool_s *wp) /* {{{ */

if (wp->set_gid) {
if (0 > setgid(wp->set_gid)) {
zlog(ZLOG_SYSERROR, "[pool %s] failed to setgid(%d)", wp->config->name, wp->set_gid);
zlog(ZLOG_SYSERROR, "[pool %s] failed to setgid(%" PRIuMAX ")", wp->config->name, (uintmax_t) wp->set_gid);
return -1;
}
}
if (wp->set_uid) {
if (0 > initgroups(wp->set_user ? wp->set_user : wp->config->user, wp->set_gid)) {
zlog(ZLOG_SYSERROR, "[pool %s] failed to initgroups(%s, %d)", wp->config->name, wp->config->user, wp->set_gid);
zlog(ZLOG_SYSERROR, "[pool %s] failed to initgroups(%s, %" PRIuMAX ")", wp->config->name, wp->config->user, (uintmax_t) wp->set_gid);
return -1;
}
if (0 > setuid(wp->set_uid)) {
zlog(ZLOG_SYSERROR, "[pool %s] failed to setuid(%d)", wp->config->name, wp->set_uid);
zlog(ZLOG_SYSERROR, "[pool %s] failed to setuid(%" PRIuMAX ")", wp->config->name, (uintmax_t) wp->set_uid);
return -1;
}
}
Expand Down
9 changes: 7 additions & 2 deletions sapi/fpm/fpm/fpm_worker_pool.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#ifndef FPM_WORKER_POOL_H
#define FPM_WORKER_POOL_H 1

#include <sys/types.h>

#include "fpm_conf.h"
#include "fpm_shm.h"

Expand All @@ -23,9 +25,12 @@ struct fpm_worker_pool_s {
char *user, *home; /* for setting env USER and HOME */
enum fpm_address_domain listen_address_domain;
int listening_socket;
int set_uid, set_gid; /* config uid and gid */
uid_t set_uid;
gid_t set_gid; /* config uid and gid */
char *set_user; /* config user name */
int socket_uid, socket_gid, socket_mode;
uid_t socket_uid;
gid_t socket_gid;
int socket_mode;

/* runtime */
struct fpm_child_s *children;
Expand Down
Loading
Loading