php-src/Zend/zend_call_stack.c
Christoph M. Becker bf1cfc0753
Revert GH-10300
Cf. <https://github.com/php/php-src/pull/10220#issuecomment-1383739816>.

This reverts commit 68ada76f9a.
his reverts commit 45384c6e20.
This reverts commit ef7fbfd710.
This reverts commit 9b9ea0d7c6.
This reverts commit f15747c26b.
This reverts commit e883ba93c4.
This reverts commit 7e87551c37.
This reverts commit 921274d2b8.
This reverts commit fc1f528e5e.
This reverts commit 0961715cda.
This reverts commit a93f264526.
This reverts commit 72dd94e1c6.
This reverts commit 29b2dc8964.
This reverts commit 05c7653bba.
This reverts commit 5190e5c260.
This reverts commit 6b55bf228c.
This reverts commit 184b4a12d3.
This reverts commit 4c31b7888a.
This reverts commit d44e9680f0.
This reverts commit 4069a5c43f.
2023-01-16 12:22:54 +01:00

458 lines
13 KiB
C

/*
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
| Copyright (c) Zend Technologies Ltd. (http://www.zend.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 2.00 of the Zend license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.zend.com/license/2_00.txt. |
| If you did not receive a copy of the Zend license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@zend.com so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Arnaud Le Blanc <arnaud.lb@gmail.com> |
+----------------------------------------------------------------------+
*/
/* Inspired from Chromium's stack_util.cc */
#include "zend.h"
#include "zend_globals.h"
#include "zend_portability.h"
#include "zend_call_stack.h"
#include <stdint.h>
#ifdef ZEND_WIN32
# include <processthreadsapi.h>
# include <memoryapi.h>
#else /* ZEND_WIN32 */
# include <sys/resource.h>
# ifdef HAVE_UNISTD_H
# include <unistd.h>
# endif
# ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
# endif
#endif /* ZEND_WIN32 */
#if defined(__linux__) || defined(__FreeBSD__) || defined(__APPLE__)
# include <pthread.h>
#endif
#ifdef __FreeBSD__
# include <pthread_np.h>
# include <sys/mman.h>
# include <sys/sysctl.h>
# include <sys/user.h>
#endif
#ifdef __linux__
#include <sys/syscall.h>
#endif
#ifdef ZEND_CHECK_STACK_LIMIT
/* Called once per process or thread */
ZEND_API void zend_call_stack_init(void) {
if (!zend_call_stack_get(&EG(call_stack))) {
EG(call_stack) = (zend_call_stack){0};
}
switch (EG(max_allowed_stack_size)) {
case ZEND_MAX_ALLOWED_STACK_SIZE_DETECT: {
void *base = EG(call_stack).base;
size_t size = EG(call_stack).max_size;
if (UNEXPECTED(base == (void*)0)) {
base = zend_call_stack_position();
size = zend_call_stack_default_size();
/* base is not the actual stack base */
size -= 32 * 1024;
}
EG(stack_base) = base;
EG(stack_limit) = zend_call_stack_limit(base, size, EG(reserved_stack_size));
break;
}
case ZEND_MAX_ALLOWED_STACK_SIZE_UNCHECKED: {
EG(stack_base) = (void*)0;
EG(stack_limit) = (void*)0;
break;
}
default: {
ZEND_ASSERT(EG(max_allowed_stack_size) > 0);
void *base = EG(call_stack).base;
if (UNEXPECTED(base == (void*)0)) {
base = zend_call_stack_position();
}
EG(stack_base) = base;
EG(stack_limit) = zend_call_stack_limit(base, EG(max_allowed_stack_size), EG(reserved_stack_size));
break;
}
}
}
#ifdef __linux__
static bool zend_call_stack_is_main_thread(void) {
# ifdef HAVE_GETTID
return getpid() == gettid();
# else
return getpid() == syscall(SYS_gettid);
# endif
}
# ifdef HAVE_PTHREAD_GETATTR_NP
static bool zend_call_stack_get_linux_pthread(zend_call_stack *stack)
{
pthread_attr_t attr;
int error;
void *addr;
size_t max_size;
/* pthread_getattr_np() will return bogus values for the main thread with
* musl or with some old glibc versions */
ZEND_ASSERT(!zend_call_stack_is_main_thread());
error = pthread_getattr_np(pthread_self(), &attr);
if (error) {
return false;
}
error = pthread_attr_getstack(&attr, &addr, &max_size);
if (error) {
return false;
}
# if defined(__GLIBC__) && (__GLIBC__ < 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8))
{
size_t guard_size;
/* In glibc prior to 2.8, addr and size include the guard pages */
error = pthread_attr_getguardsize(&attr, &guard_size);
if (error) {
return false;
}
addr = (int8_t*)addr + guard_size;
max_size -= guard_size;
}
# endif /* glibc < 2.8 */
stack->base = (int8_t*)addr + max_size;
stack->max_size = max_size;
return true;
}
# else /* HAVE_PTHREAD_GETATTR_NP */
static bool zend_call_stack_get_linux_pthread(zend_call_stack *stack)
{
return false;
}
# endif /* HAVE_PTHREAD_GETATTR_NP */
static bool zend_call_stack_get_linux_proc_maps(zend_call_stack *stack)
{
FILE *f;
char buffer[4096];
uintptr_t addr_on_stack = (uintptr_t)&buffer;
uintptr_t start, end, prev_end = 0;
size_t max_size;
bool found = false;
struct rlimit rlim;
int error;
/* This method is relevant only for the main thread */
ZEND_ASSERT(zend_call_stack_is_main_thread());
/* Scan the process memory mappings to find the one containing the stack.
*
* The end of the stack mapping is the base of the stack. The start is
* adjusted by the kernel as the stack grows. The maximum stack size is
* determined by RLIMIT_STACK and the previous mapping.
*
*
* ^ Higher addresses ^
* : :
* : :
* Mapping end --> |-------------------| <-- Stack base (stack start)
* | | ^
* | Stack Mapping | | Stack size
* | | v
* Mapping start --> |-------------------| <-- Current stack end
* (adjusted : :
* downwards as the . .
* stack grows) : :
* |-------------------|
* | Some Mapping | The previous mapping may prevent
* |-------------------| stack growth
* : :
* : :
* v Lower addresses v
*/
f = fopen("/proc/self/maps", "r");
if (!f) {
return false;
}
while (fgets(buffer, sizeof(buffer), f) && sscanf(buffer, "%" SCNxPTR "-%" SCNxPTR, &start, &end) == 2) {
if (start <= addr_on_stack && end >= addr_on_stack) {
found = true;
break;
}
prev_end = end;
}
fclose(f);
if (!found) {
return false;
}
error = getrlimit(RLIMIT_STACK, &rlim);
if (error || rlim.rlim_cur == RLIM_INFINITY) {
return false;
}
max_size = rlim.rlim_cur;
/* Previous mapping may prevent the stack from growing */
if (end - max_size < prev_end) {
max_size = prev_end - end;
}
stack->base = (void*)end;
stack->max_size = max_size;
return true;
}
static bool zend_call_stack_get_linux(zend_call_stack *stack)
{
if (zend_call_stack_is_main_thread()) {
return zend_call_stack_get_linux_proc_maps(stack);
}
return zend_call_stack_get_linux_pthread(stack);
}
#else /* __linux__ */
static bool zend_call_stack_get_linux(zend_call_stack *stack)
{
return false;
}
#endif /* __linux__ */
#ifdef __FreeBSD__
static bool zend_call_stack_is_main_thread(void)
{
int is_main = pthread_main_np();
return is_main == -1 || is_main == 1;
}
# if defined(HAVE_PTHREAD_ATTR_GET_NP) && defined(HAVE_PTHREAD_ATTR_GET_STACK)
static bool zend_call_stack_get_freebsd_pthread(zend_call_stack *stack)
{
pthread_attr_t attr;
int error;
void *addr;
size_t max_size;
size_t guard_size;
/* pthread will return bogus values for the main thread */
ZEND_ASSERT(!zend_call_stack_is_main_thread());
pthread_attr_init(&attr);
error = pthread_attr_get_np(pthread_self(), &attr);
if (error) {
goto fail;
}
error = pthread_attr_getstack(&attr, &addr, &max_size);
if (error) {
goto fail;
}
stack->base = (int8_t*)addr + max_size;
stack->max_size = max_size;
pthread_attr_destroy(&attr);
return true;
fail:
pthread_attr_destroy(&attr);
return false;
}
# else /* defined(HAVE_PTHREAD_ATTR_GET_NP) && defined(HAVE_PTHREAD_ATTR_GET_STACK) */
static bool zend_call_stack_get_freebsd_pthread(zend_call_stack *stack)
{
return false;
}
# endif /* defined(HAVE_PTHREAD_ATTR_GET_NP) && defined(HAVE_PTHREAD_ATTR_GET_STACK) */
static bool zend_call_stack_get_freebsd_sysctl(zend_call_stack *stack)
{
void *stack_base;
int mib[2] = {CTL_KERN, KERN_USRSTACK};
size_t len = sizeof(stack_base);
struct rlimit rlim;
/* This method is relevant only for the main thread */
ZEND_ASSERT(zend_call_stack_is_main_thread());
if (sysctl(mib, sizeof(mib)/sizeof(*mib), &stack_base, &len, NULL, 0) != 0) {
return false;
}
if (getrlimit(RLIMIT_STACK, &rlim) != 0) {
return false;
}
if (rlim.rlim_cur == RLIM_INFINITY) {
return false;
}
size_t guard_size = getpagesize();
stack->base = stack_base;
stack->max_size = rlim.rlim_cur - guard_size;
return true;
}
static bool zend_call_stack_get_freebsd(zend_call_stack *stack)
{
if (zend_call_stack_is_main_thread()) {
return zend_call_stack_get_freebsd_sysctl(stack);
}
return zend_call_stack_get_freebsd_pthread(stack);
}
#else
static bool zend_call_stack_get_freebsd(zend_call_stack *stack)
{
return false;
}
#endif /* __FreeBSD__ */
#ifdef ZEND_WIN32
static bool zend_call_stack_get_win32(zend_call_stack *stack)
{
ULONG_PTR low_limit, high_limit;
ULONG size;
MEMORY_BASIC_INFORMATION guard_region = {0}, uncommitted_region = {0};
size_t result_size, page_size;
/* The stack consists of three regions: committed, guard, and uncommitted.
* Memory is committed when the guard region is accessed. If only one page
* is left in the uncommitted region, a stack overflow error is raised
* instead.
*
* The total useable stack size is the size of the committed and uncommitted
* regions less one page.
*
* http://blogs.msdn.com/b/satyem/archive/2012/08/13/thread-s-stack-memory-management.aspx
* https://learn.microsoft.com/en-us/windows/win32/procthread/thread-stack-size
*
* ^ Higher addresses ^
* : :
* : :
* high_limit --> |--------------------|
* ^ | |
* | | Committed region |
* | | |
* | |------------------- | <-- guard_region.BaseAddress
* reserved | | | + guard_region.RegionSize
* size | | Guard region |
* | | |
* | |--------------------| <-- guard_region.BaseAddress,
* | | | uncommitted_region.BaseAddress
* | | Uncommitted region | + uncommitted_region.RegionSize
* v | |
* low_limit --> |------------------- | <-- uncommitted_region.BaseAddress
* : :
* : :
* v Lower addresses v
*/
GetCurrentThreadStackLimits(&low_limit, &high_limit);
result_size = VirtualQuery((void*)low_limit,
&uncommitted_region, sizeof(uncommitted_region));
ZEND_ASSERT(result_size >= sizeof(uncommitted_region));
result_size = VirtualQuery((int8_t*)uncommitted_region.BaseAddress + uncommitted_region.RegionSize,
&guard_region, sizeof(guard_region));
ZEND_ASSERT(result_size >= sizeof(uncommitted_region));
stack->base = (void*)high_limit;
stack->max_size = (uintptr_t)high_limit - (uintptr_t)low_limit;
ZEND_ASSERT(stack->max_size > guard_region.RegionSize);
stack->max_size -= guard_region.RegionSize;
/* The uncommitted region does not shrink below 1 page */
page_size = zend_get_page_size();
ZEND_ASSERT(stack->max_size > page_size);
stack->max_size -= page_size;
return true;
}
#else /* ZEND_WIN32 */
static bool zend_call_stack_get_win32(zend_call_stack *stack)
{
return false;
}
#endif /* ZEND_WIN32 */
#if defined(__APPLE__) && defined(HAVE_PTHREAD_GET_STACKADDR_NP)
static bool zend_call_stack_get_macos(zend_call_stack *stack)
{
void *base = pthread_get_stackaddr_np(pthread_self());
size_t max_size;
if (pthread_main_np()) {
/* pthread_get_stacksize_np() returns a too low value for the main
* thread in OSX 10.9, 10.10:
* https://mail.openjdk.org/pipermail/hotspot-dev/2013-October/011353.html
* https://github.com/rust-lang/rust/issues/43347
*/
/* Stack size is 8MiB by default for main threads
* https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html */
max_size = 8 * 1024 * 1024;
} else {
max_size = pthread_get_stacksize_np(pthread_self());
}
stack->base = base;
stack->max_size = max_size;
return true;
}
#else /* defined(__APPLE__) && defined(HAVE_PTHREAD_GET_STACKADDR_NP) */
static bool zend_call_stack_get_macos(zend_call_stack *stack)
{
return false;
}
#endif /* defined(__APPLE__) && defined(HAVE_PTHREAD_GET_STACKADDR_NP) */
/** Get the stack information for the calling thread */
ZEND_API bool zend_call_stack_get(zend_call_stack *stack)
{
if (zend_call_stack_get_linux(stack)) {
return true;
}
if (zend_call_stack_get_freebsd(stack)) {
return true;
}
if (zend_call_stack_get_win32(stack)) {
return true;
}
if (zend_call_stack_get_macos(stack)) {
return true;
}
return false;
}
#endif /* ZEND_CHECK_STACK_LIMIT */