carb/memory/Util.h

File members: carb/memory/Util.h

// Copyright (c) 2021-2024, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//

#pragma once

#include "../Defines.h"

#if CARB_PLATFORM_LINUX
#    include <unistd.h>
#endif

namespace carb
{
namespace memory
{

// Turn off optimization for testReadable() for Visual Studio, otherwise the read will be elided and it will always
// return true.
CARB_OPTIMIZE_OFF_MSC()

CARB_ATTRIBUTE(no_sanitize_address) inline bool testReadable(const void* mem)
{
#if CARB_PLATFORM_WINDOWS
    // Use SEH to catch a read failure. This is very fast unless an exception occurs as no setup work is needed on
    // x86_64.
    __try
    {
        size_t s = *reinterpret_cast<const size_t*>(mem);
        CARB_UNUSED(s);
        return true;
    }
    __except (1)
    {
        return false;
    }
#elif CARB_POSIX
    // some unit tests explicitly pass in `nullptr` to this function which GCC11 picks up as
    // under the `-Werror=nonnull` warning.  We'll disable that locally here since passing in
    // garbage values could be intentional.
    CARB_IGNOREWARNING_GNUC_WITH_PUSH("-Wnonnull")
    // The pipes trick: use the kernel to validate that the memory can be read. write() will return -1 with errno=EFAULT
    // if the memory is not readable.
    int pipes[2];
    CARB_FATAL_UNLESS(pipe(pipes) == 0, "Failed to create pipes");
    auto ret = CARB_RETRY_EINTR(write(pipes[1], mem, sizeof(size_t)));
    CARB_FATAL_UNLESS(
        ret == sizeof(size_t) || errno == EFAULT, "Unexpected result from write(): {%d/%s}", errno, strerror(errno));
    close(pipes[0]);
    close(pipes[1]);
    return ret == sizeof(size_t);
    CARB_IGNOREWARNING_GNUC_POP
#else
    CARB_UNSUPPORTED_PLATFORM();
#endif
}
CARB_OPTIMIZE_ON_MSC()

CARB_ATTRIBUTE(no_sanitize_address) inline bool protectedMemmove(void* dest, const void* source, size_t len)
{
    if (!source)
        return false;

#if CARB_PLATFORM_WINDOWS
    // Use SEH to catch a read failure. This is very fast unless an exception occurs as no setup work is needed on
    // x86_64.
    __try
    {
        memmove(dest, source, len);
        return true;
    }
    __except (1)
    {
        return false;
    }
#elif CARB_POSIX
    // Create a pipe and read the data through the pipe. The kernel will sanitize the reads.
    int pipes[2];
    if (pipe(pipes) != 0)
        return false;

    while (len != 0)
    {
        ssize_t s = ::carb_min((ssize_t)len, (ssize_t)4096);
        if (CARB_RETRY_EINTR(write(pipes[1], source, s)) != s || CARB_RETRY_EINTR(read(pipes[0], dest, s)) != s)
            break;
        len -= size_t(s);
        dest = static_cast<uint8_t*>(dest) + s;
        source = static_cast<const uint8_t*>(source) + s;
    }

    close(pipes[0]);
    close(pipes[1]);
    return len == 0;
#else
    CARB_UNSUPPORTED_PLATFORM();
#endif
}

CARB_ATTRIBUTE(no_sanitize_address) inline bool protectedStrncpy(char* dest, const char* source, size_t n)
{
    if (!source)
        return false;

#if CARB_PLATFORM_WINDOWS
    // Use SEH to catch a read failure. This is very fast unless an exception occurs as no setup work is needed on
    // x86_64.
    __try
    {
        size_t len = strnlen(source, n - 1);
        memcpy(dest, source, len);
        dest[len] = '\0';
        return true;
    }
    __except (1)
    {
        return false;
    }
#elif CARB_POSIX
    if (n == 0)
        return false;

    // Create a pipe and read the data through the pipe. The kernel will sanitize the reads.
    struct Pipes
    {
        bool valid;
        int fds[2];
        Pipes()
        {
            valid = pipe(fds) == 0;
        }
        ~Pipes()
        {
            if (valid)
            {
                close(fds[0]);
                close(fds[1]);
            }
        }
        int operator[](int p) const noexcept
        {
            return fds[p];
        }
    } pipes;

    if (!pipes.valid)
        return false;

    constexpr static size_t kBytes = sizeof(size_t);
    constexpr static size_t kMask = kBytes - 1;

    // Unaligned reads
    while (n != 0 && (size_t(source) & kMask) != 0)
    {
        if (CARB_RETRY_EINTR(write(pipes[1], source, 1)) != 1 || CARB_RETRY_EINTR(read(pipes[0], dest, 1)) != 1)
            return false;
        if (*dest == '\0')
            return true;
        ++source, ++dest, --n;
    }

    // Aligned reads
    while (n >= kBytes)
    {
        CARB_ASSERT((size_t(source) & kMask) == 0);
        size_t value;
        if (CARB_RETRY_EINTR(write(pipes[1], source, kBytes)) != kBytes ||
            CARB_RETRY_EINTR(read(pipes[0], &value, kBytes)) != kBytes)
            return false;
        // Use the strlen bit trick to check if any bytes that make up a word are definitely not zero
        if (CARB_UNLIKELY(((value - 0x0101010101010101) & 0x8080808080808080)))
        {
            // One of the bytes could be zero
            char chars[kBytes];
            memcpy(chars, &value, kBytes);
            for (int i = 0; i != kBytes; ++i)
            {
                dest[i] = chars[i];
                if (!dest[i])
                    return true;
            }
        }
        else
        {
            memcpy(dest, &value, kBytes);
        }
        source += kBytes;
        dest += kBytes;
        n -= kBytes;
    }

    // Trailing reads
    while (n != 0)
    {
        if (CARB_RETRY_EINTR(write(pipes[1], source, 1)) != 1 || CARB_RETRY_EINTR(read(pipes[0], dest, 1)) != 1)
            return false;
        if (*dest == '\0')
            return true;
        ++source, ++dest, --n;
    }

    // Truncate
    *(dest - 1) = '\0';
    return true;
#else
    CARB_UNSUPPORTED_PLATFORM();
#endif
}

} // namespace memory
} // namespace carb