Skip to content

Commit

Permalink
ext/pcntl: pcntl_exec() using execveat whenever possible instead.
Browse files Browse the repository at this point in the history
Allows at least to avoid original process file descriptor to leak
in the new process image.
Then with Linux 6.14, we can add another layer of security to check
beforehand if the executable can be actually safely executed.
  • Loading branch information
devnexen committed Feb 16, 2025
1 parent 0d8b139 commit d2f7d55
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 3 deletions.
2 changes: 1 addition & 1 deletion ext/pcntl/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ if test "$PHP_PCNTL" != "no"; then
done

AC_CHECK_FUNCS(m4_normalize([
forkx
execveat
getcpuid
getpriority
pidfd_open
Expand Down
57 changes: 55 additions & 2 deletions ext/pcntl/pcntl.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@
#include <sys/resource.h>
#endif

#if defined(HAVE_EXECVEAT)
#include <fcntl.h>
#include <unistd.h>
#endif

#ifdef HAVE_WAITID
#if defined (HAVE_DECL_P_ALL) && HAVE_DECL_P_ALL == 1
#define HAVE_POSIX_IDTYPES 1
Expand Down Expand Up @@ -617,6 +622,54 @@ PHP_FUNCTION(pcntl_wstopsig)
}
/* }}} */

#ifdef HAVE_EXECVEAT
static zend_always_inline zend_result php_execve(const char *path, char **argv, char **envp) {
int fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return FAILURE;
}
#ifdef AT_EXECVE_CHECK
// Linux >= 6.14 allows to check if `path` is allowed
// for execution per kernel security policy (w/o actually running it)
if (execveat(fd, "", argv, envp, AT_EMPTY_PATH | AT_EXECVE_CHECK) < 0) {
close(fd);
return FAILURE;
}
#endif
if (execveat(fd, "", argv, envp, AT_EMPTY_PATH) < 0) {
close(fd);
return FAILURE;
}
return SUCCESS;
}

static zend_always_inline zend_result php_execv(const char *path, char **argv) {
int fd = open(path, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return FAILURE;
}
#ifdef AT_EXECVE_CHECK
if (execveat(fd, "", argv, NULL, AT_EMPTY_PATH | AT_EXECVE_CHECK) < 0) {
close(fd);
return FAILURE;
}
#endif
if (execveat(fd, "", argv, NULL, AT_EMPTY_PATH) < 0) {
close(fd);
return FAILURE;
}
return SUCCESS;
}
#else
static zend_always_inline zend_result php_execve(const char *path, char **argv, char **envp) {
return execve(path, argv, envp) == 0 ? SUCCESS : FAILURE;
}

static zend_always_inline zend_result php_execv(const char *path, char **argv) {
return execv(path, argv) == 0 ? SUCCESS : FAILURE;
}
#endif

/* {{{ Executes specified program in current process space as defined by exec(2) */
PHP_FUNCTION(pcntl_exec)
{
Expand Down Expand Up @@ -716,7 +769,7 @@ PHP_FUNCTION(pcntl_exec)
} ZEND_HASH_FOREACH_END();
*(pair) = NULL;

if (execve(path, argv, envp) == -1) {
if (php_execve(path, argv, envp) == FAILURE) {
PCNTL_G(last_error) = errno;
php_error_docref(NULL, E_WARNING, "Error has occurred: (errno %d) %s", errno, strerror(errno));
}
Expand All @@ -727,7 +780,7 @@ PHP_FUNCTION(pcntl_exec)
efree(envp);
} else {

if (execv(path, argv) == -1) {
if (php_execv(path, argv) == FAILURE) {
PCNTL_G(last_error) = errno;
php_error_docref(NULL, E_WARNING, "Error has occurred: (errno %d) %s", errno, strerror(errno));
}
Expand Down

0 comments on commit d2f7d55

Please sign in to comment.