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