mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-18 18:01:39 +02:00
strerror_r: Bionic's message table covers errno 0..133 except the unassigned 41 and 58. Numbers outside that set now get "Unknown error N" without consulting the host, so the text is the same on glibc and musl and no longer relies on comparing against strerror_r(-1). atoi: glibc's atoi is (int)strtol() like Bionic's, so the host symbol is used there as before. The Bionic shim is registered only on libcs where that is not true (musl). linker_registry_test expects the matching result.
311 lines
11 KiB
C++
311 lines
11 KiB
C++
#include "compat/bionic_host_libc_runtime.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <limits.h>
|
|
#include <locale.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cerrno>
|
|
#include <climits>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#include "libc_shim/libc_shim.h"
|
|
|
|
namespace {
|
|
|
|
constexpr size_t AlignControlMessage(size_t size) {
|
|
return (size + sizeof(long) - 1U) & ~(sizeof(long) - 1U);
|
|
}
|
|
|
|
constexpr size_t ControlMessageLength(size_t payload_size) {
|
|
return AlignControlMessage(
|
|
sizeof(mocktail::compat::BionicControlMessageHeader)) +
|
|
payload_size;
|
|
}
|
|
|
|
constexpr size_t ControlMessageSpace(size_t payload_size) {
|
|
return AlignControlMessage(
|
|
sizeof(mocktail::compat::BionicControlMessageHeader)) +
|
|
AlignControlMessage(payload_size);
|
|
}
|
|
|
|
struct DestructorProbe {
|
|
std::atomic<int> calls{0};
|
|
};
|
|
|
|
void RecordThreadDestructor(void *argument) {
|
|
auto *probe = static_cast<DestructorProbe *>(argument);
|
|
probe->calls.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, WalksValidControlMessages) {
|
|
constexpr size_t kPayloadSize = sizeof(int);
|
|
using Header = mocktail::compat::BionicControlMessageHeader;
|
|
alignas(Header)
|
|
std::array<unsigned char, 2 * ControlMessageSpace(kPayloadSize)>
|
|
control{};
|
|
mocktail::compat::BionicMessageHeader message{};
|
|
message.msg_control = control.data();
|
|
message.msg_controllen = control.size();
|
|
|
|
auto *first = reinterpret_cast<Header *>(control.data());
|
|
first->cmsg_len = ControlMessageLength(kPayloadSize);
|
|
auto *second = reinterpret_cast<Header *>(control.data() +
|
|
ControlMessageSpace(kPayloadSize));
|
|
second->cmsg_len = ControlMessageLength(kPayloadSize);
|
|
|
|
EXPECT_EQ(mocktail_bionic_cmsg_nxthdr(&message, first), second);
|
|
EXPECT_EQ(mocktail_bionic_cmsg_nxthdr(&message, second), nullptr);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, RejectsMalformedControlMessages) {
|
|
using Header = mocktail::compat::BionicControlMessageHeader;
|
|
alignas(Header) std::array<unsigned char, 2 * sizeof(Header)> control{};
|
|
mocktail::compat::BionicMessageHeader message{};
|
|
message.msg_control = control.data();
|
|
message.msg_controllen = control.size();
|
|
auto *header = reinterpret_cast<Header *>(control.data());
|
|
|
|
header->cmsg_len = sizeof(Header) - 1;
|
|
EXPECT_EQ(mocktail_bionic_cmsg_nxthdr(&message, header), nullptr);
|
|
|
|
header->cmsg_len = std::numeric_limits<size_t>::max();
|
|
EXPECT_EQ(mocktail_bionic_cmsg_nxthdr(&message, header), nullptr);
|
|
|
|
header->cmsg_len = sizeof(Header);
|
|
message.msg_controllen = sizeof(Header);
|
|
EXPECT_EQ(mocktail_bionic_cmsg_nxthdr(&message, header), nullptr);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, RunsThreadDestructorAtThreadExit) {
|
|
DestructorProbe probe;
|
|
int result = -1;
|
|
std::thread worker([&probe, &result]() {
|
|
result = mocktail_bionic_cxa_thread_atexit_impl(RecordThreadDestructor,
|
|
&probe, &probe);
|
|
EXPECT_EQ(probe.calls.load(std::memory_order_relaxed), 0);
|
|
});
|
|
worker.join();
|
|
|
|
EXPECT_EQ(result, 0);
|
|
EXPECT_EQ(probe.calls.load(std::memory_order_relaxed), 1);
|
|
EXPECT_EQ(mocktail_bionic_cxa_thread_atexit_impl(nullptr, nullptr, nullptr),
|
|
EINVAL);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, FillsEveryRequestedRandomByte) {
|
|
std::array<unsigned char, 64> first;
|
|
std::array<unsigned char, 64> second;
|
|
first.fill(0xa5);
|
|
second.fill(0xa5);
|
|
|
|
mocktail_bionic_arc4random_buf(first.data(), first.size());
|
|
mocktail_bionic_arc4random_buf(second.data(), second.size());
|
|
mocktail_bionic_arc4random_buf(nullptr, 0);
|
|
|
|
EXPECT_FALSE(std::all_of(first.begin(), first.end(),
|
|
[](unsigned char value) { return value == 0xa5; }));
|
|
EXPECT_NE(first, second);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, ReturnsAbiSizedAllocatorSnapshot) {
|
|
static_assert(sizeof(mocktail::compat::BionicMallinfoSnapshot) == 80);
|
|
const auto snapshot = mocktail_bionic_mallinfo();
|
|
if (!mocktail::compat::BionicMallinfoHasHostTelemetry()) {
|
|
EXPECT_EQ(snapshot.arena, 0U);
|
|
EXPECT_EQ(snapshot.ordblks, 0U);
|
|
EXPECT_EQ(snapshot.smblks, 0U);
|
|
EXPECT_EQ(snapshot.hblks, 0U);
|
|
EXPECT_EQ(snapshot.hblkhd, 0U);
|
|
EXPECT_EQ(snapshot.usmblks, 0U);
|
|
EXPECT_EQ(snapshot.fsmblks, 0U);
|
|
EXPECT_EQ(snapshot.uordblks, 0U);
|
|
EXPECT_EQ(snapshot.fordblks, 0U);
|
|
EXPECT_EQ(snapshot.keepcost, 0U);
|
|
}
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, PreservesValidSysInfoCounters) {
|
|
struct sysinfo info{};
|
|
info.totalram = 8UL * 1024UL * 1024UL * 1024UL;
|
|
info.freeram = 2UL * 1024UL * 1024UL * 1024UL;
|
|
info.totalswap = 4UL * 1024UL * 1024UL * 1024UL;
|
|
info.freeswap = 1UL * 1024UL * 1024UL * 1024UL;
|
|
|
|
mocktail::compat::NormalizeBionicSysInfo(&info);
|
|
|
|
EXPECT_EQ(info.totalram, 8UL * 1024UL * 1024UL * 1024UL);
|
|
EXPECT_EQ(info.freeram, 2UL * 1024UL * 1024UL * 1024UL);
|
|
EXPECT_EQ(info.totalswap, 4UL * 1024UL * 1024UL * 1024UL);
|
|
EXPECT_EQ(info.freeswap, 1UL * 1024UL * 1024UL * 1024UL);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, ClearsLinuxulatorUnderflowedSwapCounters) {
|
|
struct sysinfo info{};
|
|
info.totalram = 8UL * 1024UL * 1024UL * 1024UL;
|
|
info.freeram = 2UL * 1024UL * 1024UL * 1024UL;
|
|
info.totalswap = std::numeric_limits<unsigned long>::max() - 40959UL;
|
|
info.freeswap = std::numeric_limits<unsigned long>::max() - 11550719UL;
|
|
|
|
mocktail::compat::NormalizeBionicSysInfo(&info);
|
|
|
|
EXPECT_EQ(info.totalram, 8UL * 1024UL * 1024UL * 1024UL);
|
|
EXPECT_EQ(info.freeram, 2UL * 1024UL * 1024UL * 1024UL);
|
|
EXPECT_EQ(info.totalswap, 0UL);
|
|
EXPECT_EQ(info.freeswap, 0UL);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, CallsHostSysInfoThroughBionicAdapter) {
|
|
struct sysinfo info{};
|
|
ASSERT_EQ(mocktail_bionic_sysinfo(&info), 0);
|
|
EXPECT_GT(info.totalram, 0UL);
|
|
EXPECT_LE(info.freeswap, info.totalswap);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, NormalizesContradictoryLinuxulatorUname) {
|
|
struct utsname name{};
|
|
std::snprintf(name.sysname, sizeof(name.sysname), "Linux");
|
|
std::snprintf(name.release, sizeof(name.release), "5.15.0");
|
|
std::snprintf(name.version, sizeof(name.version),
|
|
"FreeBSD 15.1-RELEASE-p2 GENERIC");
|
|
|
|
EXPECT_TRUE(mocktail::compat::NormalizeLinuxulatorUnameVersion(
|
|
&name, "#4 SMP PREEMPT_DYNAMIC"));
|
|
EXPECT_STREQ(name.sysname, "Linux");
|
|
EXPECT_STREQ(name.release, "5.15.0");
|
|
EXPECT_STREQ(name.version, "#4 SMP PREEMPT_DYNAMIC");
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, PreservesNativeLinuxUname) {
|
|
struct utsname name{};
|
|
std::snprintf(name.sysname, sizeof(name.sysname), "Linux");
|
|
std::snprintf(name.version, sizeof(name.version), "#1 SMP PREEMPT_DYNAMIC");
|
|
|
|
EXPECT_FALSE(mocktail::compat::NormalizeLinuxulatorUnameVersion(
|
|
&name, "#4 SMP PREEMPT_DYNAMIC"));
|
|
EXPECT_STREQ(name.version, "#1 SMP PREEMPT_DYNAMIC");
|
|
|
|
ASSERT_EQ(mocktail_bionic_uname(&name), 0);
|
|
EXPECT_STREQ(name.sysname, "Linux");
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, HidesPrivilegedHostIdentityFromAndroidGuest) {
|
|
EXPECT_EQ(mocktail::compat::NormalizeBionicApplicationUid(0), 1000U);
|
|
EXPECT_EQ(mocktail::compat::NormalizeBionicApplicationUid(1000), 1000U);
|
|
EXPECT_EQ(mocktail::compat::NormalizeBionicApplicationUid(12345), 12345U);
|
|
EXPECT_EQ(mocktail_bionic_getuid(),
|
|
mocktail::compat::NormalizeBionicApplicationUid(::getuid()));
|
|
EXPECT_EQ(mocktail_bionic_geteuid(),
|
|
mocktail::compat::NormalizeBionicApplicationUid(::geteuid()));
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, ParsesAtoiWithBionicOverflowBehavior) {
|
|
EXPECT_EQ(mocktail_bionic_atoi(""), 0);
|
|
EXPECT_EQ(mocktail_bionic_atoi("invalid"), 0);
|
|
EXPECT_EQ(mocktail_bionic_atoi(" -42suffix"), -42);
|
|
EXPECT_EQ(mocktail_bionic_atoi("2147483647"), INT_MAX);
|
|
EXPECT_EQ(mocktail_bionic_atoi("-2147483648"), INT_MIN);
|
|
EXPECT_EQ(mocktail_bionic_atoi("2147483648"), INT_MIN);
|
|
EXPECT_EQ(mocktail_bionic_atoi("4294967295"), -1);
|
|
EXPECT_EQ(mocktail_bionic_atoi("9999999999"), 1410065407);
|
|
|
|
errno = 0;
|
|
EXPECT_EQ(mocktail_bionic_atoi("9223372036854775808"), -1);
|
|
EXPECT_EQ(errno, ERANGE);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, UsesBionicPosixStrErrorContract) {
|
|
std::array<char, 128> buffer{};
|
|
|
|
errno = EDOM;
|
|
EXPECT_EQ(mocktail_bionic_strerror_r(EINVAL, buffer.data(), buffer.size()),
|
|
0);
|
|
EXPECT_NE(std::strstr(buffer.data(), "Invalid"), nullptr);
|
|
EXPECT_EQ(errno, EDOM);
|
|
|
|
EXPECT_EQ(mocktail_bionic_strerror_r(0, buffer.data(), buffer.size()), 0);
|
|
EXPECT_STREQ(buffer.data(), std::strerror(0));
|
|
EXPECT_EQ(errno, EDOM);
|
|
|
|
for (const int error_number : {-1, 41, 58, 134, 4567, INT_MIN, INT_MAX}) {
|
|
errno = EDOM;
|
|
ASSERT_EQ(mocktail_bionic_strerror_r(error_number, buffer.data(),
|
|
buffer.size()), 0);
|
|
EXPECT_EQ(std::string(buffer.data()),
|
|
"Unknown error " + std::to_string(error_number));
|
|
EXPECT_EQ(errno, EDOM);
|
|
}
|
|
|
|
std::array<char, 2> truncated{};
|
|
errno = 0;
|
|
EXPECT_EQ(
|
|
mocktail_bionic_strerror_r(4567, truncated.data(), truncated.size()), -1);
|
|
EXPECT_EQ(truncated[1], '\0');
|
|
EXPECT_EQ(errno, ERANGE);
|
|
|
|
errno = 0;
|
|
EXPECT_EQ(mocktail_bionic_strerror_r(EINVAL, nullptr, 0), -1);
|
|
EXPECT_EQ(errno, ERANGE);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, ParsesIntegersInScopedCLocale) {
|
|
const locale_t original_locale = uselocale(nullptr);
|
|
auto guest_locale =
|
|
reinterpret_cast<mocktail::compat::BionicLocale>(uintptr_t{1});
|
|
|
|
char *signed_end = nullptr;
|
|
errno = EDOM;
|
|
EXPECT_EQ(
|
|
mocktail_bionic_strtoll_l("-0x7f-rest", &signed_end, 0, guest_locale),
|
|
-127);
|
|
EXPECT_STREQ(signed_end, "-rest");
|
|
EXPECT_EQ(errno, EDOM);
|
|
|
|
char *unsigned_end = nullptr;
|
|
errno = 0;
|
|
EXPECT_EQ(mocktail_bionic_strtoull_l("18446744073709551616!", &unsigned_end,
|
|
10, guest_locale),
|
|
ULLONG_MAX);
|
|
EXPECT_EQ(errno, ERANGE);
|
|
EXPECT_STREQ(unsigned_end, "!");
|
|
EXPECT_EQ(uselocale(nullptr), original_locale);
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, FortifiedReadlinkAcceptsInBoundsRequest) {
|
|
std::array<char, PATH_MAX> executable{};
|
|
const ssize_t length =
|
|
mocktail___readlink_chk("/proc/self/exe", executable.data(),
|
|
executable.size(), executable.size());
|
|
ASSERT_GT(length, 0);
|
|
EXPECT_LT(static_cast<size_t>(length), executable.size());
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, FortifiedReadlinkAbortsOnBufferOverflow) {
|
|
EXPECT_DEATH(
|
|
{
|
|
char buffer[4]{};
|
|
mocktail___readlink_chk("/proc/self/exe", buffer, 5, sizeof(buffer));
|
|
},
|
|
"FORTIFY: readlink");
|
|
}
|
|
|
|
TEST(BionicHostLibcRuntimeTest, FortifiedReadlinkAbortsAboveSsizeLimit) {
|
|
EXPECT_DEATH(
|
|
{
|
|
char buffer{};
|
|
mocktail___readlink_chk("/proc/self/exe", &buffer,
|
|
static_cast<size_t>(SSIZE_MAX) + size_t{1},
|
|
std::numeric_limits<size_t>::max());
|
|
},
|
|
"FORTIFY: readlink");
|
|
}
|
|
|
|
} // namespace
|