Revision control
++ // 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