Revision control
Copy as Markdown
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:low-level-memory-management
#include <private/qv4stacklimits_p.h>
#include <private/qobject_p.h>
#include <private/qthread_p.h>
#include <QtCore/qfile.h>
#if defined(Q_OS_UNIX)
# include <pthread.h>
#endif
#ifdef Q_OS_WIN
# include <QtCore/qt_windows.h>
#elif defined(Q_OS_FREEBSD_KERNEL) || defined(Q_OS_OPENBSD)
# include <pthread_np.h>
#elif defined(Q_OS_LINUX)
# include <unistd.h>
# include <sys/resource.h> // for getrlimit()
# include <sys/syscall.h> // for SYS_gettid
# if defined(__GLIBC__) && QT_CONFIG(dlopen)
# include <dlfcn.h>
# endif
#elif defined(Q_OS_DARWIN)
# include <sys/resource.h> // for getrlimit()
#elif defined(Q_OS_QNX)
# include <devctl.h>
# include <sys/procfs.h>
# include <sys/types.h>
# include <unistd.h>
# include <fcntl.h>
#elif defined(Q_OS_INTEGRITY)
# include <INTEGRITY.h>
#elif defined(Q_OS_VXWORKS)
# include <taskLib.h>
#elif defined(Q_OS_WASM)
# include <emscripten/stack.h>
#endif
QT_BEGIN_NAMESPACE
namespace QV4 {
enum StackDefaults : qsizetype {
// Default safety margin at the end of the usable stack.
// Since we don't check the stack on every instruction, we might overrun our soft limit.
DefaultSafetyMargin = 128 * 1024,
#if defined(Q_OS_IOS)
PlatformStackSize = 1024 * 1024,
PlatformSafetyMargin = DefaultSafetyMargin,
#elif defined(Q_OS_MACOS)
PlatformStackSize = 8 * 1024 * 1024,
PlatformSafetyMargin = DefaultSafetyMargin,
#elif defined(Q_OS_ANDROID)
// Android appears to have 1MB stacks.
PlatformStackSize = 1024 * 1024,
PlatformSafetyMargin = DefaultSafetyMargin,
#elif defined(Q_OS_LINUX)
// On linux, we assume 8MB stacks if rlimit doesn't work.
PlatformStackSize = 8 * 1024 * 1024,
PlatformSafetyMargin = DefaultSafetyMargin,
#elif defined(Q_OS_QNX)
// QNX's stack is only 512k by default
PlatformStackSize = 512 * 1024,
PlatformSafetyMargin = DefaultSafetyMargin,
#else
// We try to claim 512k if we don't know anything else.
PlatformStackSize = 512 * 1024,
PlatformSafetyMargin = DefaultSafetyMargin,
#endif
};
static StackProperties createStackProperties(void *base, qsizetype size = PlatformStackSize, qsizetype margin = PlatformSafetyMargin)
{
return StackProperties {
base,
incrementStackPointer(base, size - margin),
incrementStackPointer(base, size),
};
}
#if defined(Q_OS_DARWIN) || defined(Q_OS_LINUX)
// On linux and darwin, on the main thread, the pthread functions
// may not return the true stack size since the main thread stack
// may grow. Use rlimit instead. rlimit does not work for secondary
// threads, though. If getrlimit fails, we assume the platform
// stack size.
static qsizetype getMainStackSizeFromRlimit()
{
rlimit limit;
return (getrlimit(RLIMIT_STACK, &limit) == 0 && limit.rlim_cur != RLIM_INFINITY)
? qsizetype(limit.rlim_cur)
: qsizetype(PlatformStackSize);
}
#endif
#if defined(Q_OS_INTEGRITY)
StackProperties stackProperties()
{
Address stackLow, stackHigh;
CheckSuccess(GetTaskStackLimits(CurrentTask(), &stackLow, &stackHigh));
# if Q_STACK_GROWTH_DIRECTION < 0
return createStackProperties(reinterpret_cast<void *>(stackHigh), stackHigh - stackLow);
# else
return createStackProperties(reinterpret_cast<void *>(stackLow), stackHigh - stackLow);
# endif
}
#elif defined(Q_OS_DARWIN)
StackProperties stackProperties()
{
pthread_t thread = pthread_self();
return createStackProperties(
pthread_get_stackaddr_np(thread),
pthread_main_np()
? getMainStackSizeFromRlimit()
: qsizetype(pthread_get_stacksize_np(thread)));
}
#elif defined(Q_OS_WIN)
static_assert(Q_STACK_GROWTH_DIRECTION < 0);
StackProperties stackProperties()
{
// MinGW complains about out of bounds array access in compiler headers
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Warray-bounds")
// Get the stack base.
# ifdef _WIN64
PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb());
# else
PNT_TIB pTib = reinterpret_cast<PNT_TIB>(NtCurrentTeb());
# endif
QT_WARNING_POP
quint8 *stackBase = reinterpret_cast<quint8 *>(pTib->StackBase);
// Get the stack limit. tib->StackLimit is the size of the
// currently mapped stack. The address space is larger.
MEMORY_BASIC_INFORMATION mbi = {};
if (!VirtualQuery(&mbi, &mbi, sizeof(mbi)))
qFatal("Could not retrieve memory information for stack.");
quint8 *stackLimit = reinterpret_cast<quint8 *>(mbi.AllocationBase);
return createStackProperties(stackBase, qsizetype(stackBase - stackLimit));
}
#elif defined(Q_OS_OPENBSD)
StackProperties stackProperties()
{
// From the OpenBSD docs:
//
// The pthread_stackseg_np() function returns information about the given thread's stack.
// A stack_t is the same as a struct sigaltstack (see sigaltstack(2)) except the ss_sp
// variable points to the top of the stack instead of the base.
//
// Since the example in the sigaltstack(2) documentation shows ss_sp being assigned the result
// of a malloc() call, we can assume that "top of the stack" means "the highest address", not
// the logical top of the stack.
stack_t ss;
rc = pthread_stackseg_np(pthread_self, &ss);
#if Q_STACK_GROWTH_DIRECTION < 0
return createStackProperties(ss.ss_sp);
#else
return createStackProperties(decrementStackPointer(ss.ss_sp, ss.ss_size));
#endif
}
#elif defined(Q_OS_QNX)
StackProperties stackProperties()
{
const auto tid = pthread_self();
procfs_status status;
status.tid = tid;
const int fd = open("/proc/self/ctl", O_RDONLY);
if (fd == -1)
qFatal("Could not open /proc/self/ctl");
const auto guard = qScopeGuard([fd]() { close(fd); });
if (devctl(fd, DCMD_PROC_TIDSTATUS, &status, sizeof(status), 0) != EOK)
qFatal("Could not query thread status for current thread");
if (status.tid != tid)
qFatal("Thread status query returned garbage");
#if Q_STACK_GROWTH_DIRECTION < 0
return createStackProperties(
decrementStackPointer(reinterpret_cast<void *>(status.stkbase), status.stksize),
status.stksize);
#else
return createStackProperties(reinterpret_cast<void *>(status.stkbase), status.stksize);
#endif
}
#elif defined(Q_OS_WASM)
StackProperties stackProperties()
{
const uintptr_t base = emscripten_stack_get_base();
const uintptr_t end = emscripten_stack_get_end();
const size_t size = base - end;
return createStackProperties(reinterpret_cast<void *>(base), size);
}
#elif defined(Q_OS_VXWORKS)
StackProperties stackProperties()
{
TASK_DESC taskDescription;
taskInfoGet(taskIdSelf(), &taskDescription);
return createStackProperties(taskDescription.td_pStackBase, taskDescription.td_stackSize,
taskDescription.td_stackSize / 8);
}
#else
StackProperties stackPropertiesGeneric(qsizetype stackSize = 0)
{
// If stackSize is given, do not trust the stack size returned by pthread_attr_getstack
pthread_t thread = pthread_self();
pthread_attr_t sattr;
# if defined(PTHREAD_NP_H) || defined(_PTHREAD_NP_H_) || defined(Q_OS_NETBSD)
pthread_attr_init(&sattr);
pthread_attr_get_np(thread, &sattr);
# else
pthread_getattr_np(thread, &sattr);
# endif
// pthread_attr_getstack returns the address of the memory region, which is the physical
// base of the stack, not the logical one.
void *stackBase;
size_t regionSize;
int rc = pthread_attr_getstack(&sattr, &stackBase, ®ionSize);
pthread_attr_destroy(&sattr);
if (rc)
qFatal("Cannot find stack base");
# if Q_STACK_GROWTH_DIRECTION < 0
stackBase = decrementStackPointer(stackBase, regionSize);
# endif
return createStackProperties(stackBase, stackSize ? stackSize : regionSize);
}
#if defined(Q_OS_LINUX)
static void *stackBaseFromLibc()
{
#if defined(__GLIBC__) && QT_CONFIG(dlopen)
void **libcStackEnd = static_cast<void **>(dlsym(RTLD_DEFAULT, "__libc_stack_end"));
if (!libcStackEnd)
return nullptr;
if (void *stackBase = *libcStackEnd)
return stackBase;
#endif
return nullptr;
}
struct StackSegment {
quintptr base;
quintptr limit;
};
static StackSegment stackSegmentFromProc()
{
QFile maps(QStringLiteral("/proc/self/maps"));
if (!maps.open(QIODevice::ReadOnly))
return {0, 0};
const quintptr stackAddr = reinterpret_cast<quintptr>(&maps);
char buffer[1024];
while (true) {
const qint64 length = maps.readLine(buffer, 1024);
if (length <= 0)
break;
const QByteArrayView line(buffer, length);
bool ok = false;
const qsizetype boundary = line.indexOf('-');
if (boundary < 0)
continue;
const quintptr base = line.sliced(0, boundary).toULongLong(&ok, 16);
if (!ok || base > stackAddr)
continue;
const qsizetype end = line.indexOf(' ', boundary);
if (end < 0)
continue;
const quintptr limit = line.sliced(boundary + 1, end - boundary - 1).toULongLong(&ok, 16);
if (!ok || limit <= stackAddr)
continue;
return {base, limit};
}
return {0, 0};
}
StackProperties stackProperties()
{
if (getpid() != static_cast<pid_t>(syscall(SYS_gettid)))
return stackPropertiesGeneric();
// On linux (including android), the pthread functions are expensive
// and unreliable on the main thread.
// First get the stack size from rlimit
const qsizetype stackSize = getMainStackSizeFromRlimit();
// If we have glibc and libdl, we can query a special symbol in glibc to find the base.
// That is extremely cheap, compared to all other options.
if (stackSize) {
if (void *base = stackBaseFromLibc())
return createStackProperties(base, stackSize);
}
// Try to read the stack segment from /proc/self/maps if possible.
const StackSegment segment = stackSegmentFromProc();
if (segment.base) {
# if Q_STACK_GROWTH_DIRECTION > 0
void *stackBase = reinterpret_cast<void *>(segment.base);
# else
void *stackBase = reinterpret_cast<void *>(segment.limit);
# endif
return createStackProperties(
stackBase, stackSize ? stackSize : segment.limit - segment.base);
}
// If we can't read /proc/self/maps, use the pthread functions after all, but
// override the stackSize. The main thread can grow its stack, and the pthread
// functions typically return the currently allocated stack size.
return stackPropertiesGeneric(stackSize);
}
#else // Q_OS_LINUX
StackProperties stackProperties() { return stackPropertiesGeneric(); }
#endif // Q_OS_LINUX
#endif
} // namespace QV4
QT_END_NAMESPACE