13fa38318SNico Weber //===-- wrappers_c_test.cpp -------------------------------------*- C++ -*-===//
23fa38318SNico Weber //
33fa38318SNico Weber // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43fa38318SNico Weber // See https://llvm.org/LICENSE.txt for license information.
53fa38318SNico Weber // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63fa38318SNico Weber //
73fa38318SNico Weber //===----------------------------------------------------------------------===//
83fa38318SNico Weber
901c02abcSRiley #include "common.h"
101e6d1353SVitaly Buka #include "memtag.h"
11b83417aaSPeter Collingbourne #include "scudo/interface.h"
120d3d4d3bSKostya Kortchinsky #include "tests/scudo_unit_test.h"
133fa38318SNico Weber
140d3d4d3bSKostya Kortchinsky #include <errno.h>
153fa38318SNico Weber #include <limits.h>
163fa38318SNico Weber #include <malloc.h>
17161cca26SKostya Kortchinsky #include <stdlib.h>
183fa38318SNico Weber #include <unistd.h>
1901c02abcSRiley #include <vector>
203fa38318SNico Weber
2182fc4cc6SVitaly Buka #ifndef __GLIBC_PREREQ
2282fc4cc6SVitaly Buka #define __GLIBC_PREREQ(x, y) 0
2382fc4cc6SVitaly Buka #endif
2482fc4cc6SVitaly Buka
25c981f0b4SChristopher Ferris #if SCUDO_FUCHSIA
26c981f0b4SChristopher Ferris // Fuchsia only has valloc
27c981f0b4SChristopher Ferris #define HAVE_VALLOC 1
28c981f0b4SChristopher Ferris #elif SCUDO_ANDROID
29c981f0b4SChristopher Ferris // Android only has pvalloc/valloc on 32 bit
30c981f0b4SChristopher Ferris #if !defined(__LP64__)
31c981f0b4SChristopher Ferris #define HAVE_PVALLOC 1
32c981f0b4SChristopher Ferris #define HAVE_VALLOC 1
33c981f0b4SChristopher Ferris #endif // !defined(__LP64__)
34c981f0b4SChristopher Ferris #else
35c981f0b4SChristopher Ferris // All others assumed to support both functions.
36c981f0b4SChristopher Ferris #define HAVE_PVALLOC 1
37c981f0b4SChristopher Ferris #define HAVE_VALLOC 1
38c981f0b4SChristopher Ferris #endif
39c981f0b4SChristopher Ferris
4088852964SChia-hung Duan extern "C" {
4188852964SChia-hung Duan void malloc_enable(void);
4288852964SChia-hung Duan void malloc_disable(void);
4388852964SChia-hung Duan int malloc_iterate(uintptr_t base, size_t size,
4488852964SChia-hung Duan void (*callback)(uintptr_t base, size_t size, void *arg),
4588852964SChia-hung Duan void *arg);
4688852964SChia-hung Duan void *valloc(size_t size);
4788852964SChia-hung Duan void *pvalloc(size_t size);
4888852964SChia-hung Duan
4988852964SChia-hung Duan #ifndef SCUDO_ENABLE_HOOKS_TESTS
5088852964SChia-hung Duan #define SCUDO_ENABLE_HOOKS_TESTS 0
5188852964SChia-hung Duan #endif
5288852964SChia-hung Duan
5388852964SChia-hung Duan #if (SCUDO_ENABLE_HOOKS_TESTS == 1) && (SCUDO_ENABLE_HOOKS == 0)
5488852964SChia-hung Duan #error "Hooks tests should have hooks enabled as well!"
5588852964SChia-hung Duan #endif
5688852964SChia-hung Duan
574f76810dSChia-hung Duan struct AllocContext {
584f76810dSChia-hung Duan void *Ptr;
594f76810dSChia-hung Duan size_t Size;
604f76810dSChia-hung Duan };
614f76810dSChia-hung Duan struct DeallocContext {
624f76810dSChia-hung Duan void *Ptr;
634f76810dSChia-hung Duan };
64*58c2a4e8SChiaHungDuan struct ReallocContext {
65*58c2a4e8SChiaHungDuan void *AllocPtr;
66*58c2a4e8SChiaHungDuan void *DeallocPtr;
67*58c2a4e8SChiaHungDuan size_t Size;
68*58c2a4e8SChiaHungDuan };
694f76810dSChia-hung Duan static AllocContext AC;
704f76810dSChia-hung Duan static DeallocContext DC;
71*58c2a4e8SChiaHungDuan static ReallocContext RC;
724f76810dSChia-hung Duan
7388852964SChia-hung Duan #if (SCUDO_ENABLE_HOOKS_TESTS == 1)
__scudo_allocate_hook(void * Ptr,size_t Size)744f76810dSChia-hung Duan __attribute__((visibility("default"))) void __scudo_allocate_hook(void *Ptr,
754f76810dSChia-hung Duan size_t Size) {
764f76810dSChia-hung Duan AC.Ptr = Ptr;
774f76810dSChia-hung Duan AC.Size = Size;
784f76810dSChia-hung Duan }
__scudo_deallocate_hook(void * Ptr)794f76810dSChia-hung Duan __attribute__((visibility("default"))) void __scudo_deallocate_hook(void *Ptr) {
804f76810dSChia-hung Duan DC.Ptr = Ptr;
814f76810dSChia-hung Duan }
82*58c2a4e8SChiaHungDuan __attribute__((visibility("default"))) void
__scudo_realloc_allocate_hook(void * OldPtr,void * NewPtr,size_t Size)83*58c2a4e8SChiaHungDuan __scudo_realloc_allocate_hook(void *OldPtr, void *NewPtr, size_t Size) {
84*58c2a4e8SChiaHungDuan // Verify that __scudo_realloc_deallocate_hook is called first and set the
85*58c2a4e8SChiaHungDuan // right pointer.
86*58c2a4e8SChiaHungDuan EXPECT_EQ(OldPtr, RC.DeallocPtr);
87*58c2a4e8SChiaHungDuan RC.AllocPtr = NewPtr;
88*58c2a4e8SChiaHungDuan RC.Size = Size;
89*58c2a4e8SChiaHungDuan
90*58c2a4e8SChiaHungDuan // Note that this is only used for testing. In general, only one pair of hooks
91*58c2a4e8SChiaHungDuan // will be invoked in `realloc`. if __scudo_realloc_*_hook are not defined,
92*58c2a4e8SChiaHungDuan // it'll call the general hooks only. To make the test easier, we call the
93*58c2a4e8SChiaHungDuan // general one here so that either case (whether __scudo_realloc_*_hook are
94*58c2a4e8SChiaHungDuan // defined) will be verified without separating them into different tests.
95*58c2a4e8SChiaHungDuan __scudo_allocate_hook(NewPtr, Size);
96*58c2a4e8SChiaHungDuan }
97*58c2a4e8SChiaHungDuan __attribute__((visibility("default"))) void
__scudo_realloc_deallocate_hook(void * Ptr)98*58c2a4e8SChiaHungDuan __scudo_realloc_deallocate_hook(void *Ptr) {
99*58c2a4e8SChiaHungDuan RC.DeallocPtr = Ptr;
100*58c2a4e8SChiaHungDuan
101*58c2a4e8SChiaHungDuan // See the comment in the __scudo_realloc_allocate_hook above.
102*58c2a4e8SChiaHungDuan __scudo_deallocate_hook(Ptr);
103*58c2a4e8SChiaHungDuan }
10488852964SChia-hung Duan #endif // (SCUDO_ENABLE_HOOKS_TESTS == 1)
1053e5360f1SKostya Kortchinsky }
1063e5360f1SKostya Kortchinsky
10788852964SChia-hung Duan class ScudoWrappersCTest : public Test {
10888852964SChia-hung Duan protected:
SetUp()10988852964SChia-hung Duan void SetUp() override {
11088852964SChia-hung Duan if (SCUDO_ENABLE_HOOKS && !SCUDO_ENABLE_HOOKS_TESTS)
11188852964SChia-hung Duan printf("Hooks are enabled but hooks tests are disabled.\n");
11288852964SChia-hung Duan }
11388852964SChia-hung Duan
invalidateHookPtrs()11475867f8eSChiaHungDuan void invalidateHookPtrs() {
11575867f8eSChiaHungDuan if (SCUDO_ENABLE_HOOKS_TESTS) {
11675867f8eSChiaHungDuan void *InvalidPtr = reinterpret_cast<void *>(0xdeadbeef);
11775867f8eSChiaHungDuan AC.Ptr = InvalidPtr;
11875867f8eSChiaHungDuan DC.Ptr = InvalidPtr;
119*58c2a4e8SChiaHungDuan RC.AllocPtr = RC.DeallocPtr = InvalidPtr;
12075867f8eSChiaHungDuan }
12188852964SChia-hung Duan }
verifyAllocHookPtr(UNUSED void * Ptr)12288852964SChia-hung Duan void verifyAllocHookPtr(UNUSED void *Ptr) {
12388852964SChia-hung Duan if (SCUDO_ENABLE_HOOKS_TESTS)
12488852964SChia-hung Duan EXPECT_EQ(Ptr, AC.Ptr);
12588852964SChia-hung Duan }
verifyAllocHookSize(UNUSED size_t Size)12688852964SChia-hung Duan void verifyAllocHookSize(UNUSED size_t Size) {
12788852964SChia-hung Duan if (SCUDO_ENABLE_HOOKS_TESTS)
12888852964SChia-hung Duan EXPECT_EQ(Size, AC.Size);
12988852964SChia-hung Duan }
verifyDeallocHookPtr(UNUSED void * Ptr)13088852964SChia-hung Duan void verifyDeallocHookPtr(UNUSED void *Ptr) {
13188852964SChia-hung Duan if (SCUDO_ENABLE_HOOKS_TESTS)
13288852964SChia-hung Duan EXPECT_EQ(Ptr, DC.Ptr);
13388852964SChia-hung Duan }
verifyReallocHookPtrs(UNUSED void * OldPtr,void * NewPtr,size_t Size)134*58c2a4e8SChiaHungDuan void verifyReallocHookPtrs(UNUSED void *OldPtr, void *NewPtr, size_t Size) {
135*58c2a4e8SChiaHungDuan if (SCUDO_ENABLE_HOOKS_TESTS) {
136*58c2a4e8SChiaHungDuan EXPECT_EQ(OldPtr, RC.DeallocPtr);
137*58c2a4e8SChiaHungDuan EXPECT_EQ(NewPtr, RC.AllocPtr);
138*58c2a4e8SChiaHungDuan EXPECT_EQ(Size, RC.Size);
139*58c2a4e8SChiaHungDuan }
140*58c2a4e8SChiaHungDuan }
14188852964SChia-hung Duan };
14288852964SChia-hung Duan using ScudoWrappersCDeathTest = ScudoWrappersCTest;
14388852964SChia-hung Duan
1443fa38318SNico Weber // Note that every C allocation function in the test binary will be fulfilled
1453fa38318SNico Weber // by Scudo (this includes the gtest APIs, etc.), which is a test by itself.
1463fa38318SNico Weber // But this might also lead to unexpected side-effects, since the allocation and
1473fa38318SNico Weber // deallocation operations in the TEST functions will coexist with others (see
1483fa38318SNico Weber // the EXPECT_DEATH comment below).
1493fa38318SNico Weber
1503fa38318SNico Weber // We have to use a small quarantine to make sure that our double-free tests
1513fa38318SNico Weber // trigger. Otherwise EXPECT_DEATH ends up reallocating the chunk that was just
1523fa38318SNico Weber // freed (this depends on the size obviously) and the following free succeeds.
1533fa38318SNico Weber
1543fa38318SNico Weber static const size_t Size = 100U;
1553fa38318SNico Weber
TEST_F(ScudoWrappersCDeathTest,Malloc)15688852964SChia-hung Duan TEST_F(ScudoWrappersCDeathTest, Malloc) {
1573fa38318SNico Weber void *P = malloc(Size);
1583fa38318SNico Weber EXPECT_NE(P, nullptr);
1593fa38318SNico Weber EXPECT_LE(Size, malloc_usable_size(P));
1603fa38318SNico Weber EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % FIRST_32_SECOND_64(8U, 16U), 0U);
16188852964SChia-hung Duan verifyAllocHookPtr(P);
16288852964SChia-hung Duan verifyAllocHookSize(Size);
163bed88824SLeonard Chan
164bed88824SLeonard Chan // An update to this warning in Clang now triggers in this line, but it's ok
165bed88824SLeonard Chan // because the check is expecting a bad pointer and should fail.
166bed88824SLeonard Chan #if defined(__has_warning) && __has_warning("-Wfree-nonheap-object")
167bed88824SLeonard Chan #pragma GCC diagnostic push
168bed88824SLeonard Chan #pragma GCC diagnostic ignored "-Wfree-nonheap-object"
169bed88824SLeonard Chan #endif
1703fa38318SNico Weber EXPECT_DEATH(
1713fa38318SNico Weber free(reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(P) | 1U)), "");
172bed88824SLeonard Chan #if defined(__has_warning) && __has_warning("-Wfree-nonheap-object")
173bed88824SLeonard Chan #pragma GCC diagnostic pop
174bed88824SLeonard Chan #endif
175bed88824SLeonard Chan
1763fa38318SNico Weber free(P);
17788852964SChia-hung Duan verifyDeallocHookPtr(P);
1783fa38318SNico Weber EXPECT_DEATH(free(P), "");
1793fa38318SNico Weber
1803fa38318SNico Weber P = malloc(0U);
1813fa38318SNico Weber EXPECT_NE(P, nullptr);
1823fa38318SNico Weber free(P);
1833fa38318SNico Weber
1843fa38318SNico Weber errno = 0;
1853fa38318SNico Weber EXPECT_EQ(malloc(SIZE_MAX), nullptr);
1863fa38318SNico Weber EXPECT_EQ(errno, ENOMEM);
1873fa38318SNico Weber }
1883fa38318SNico Weber
TEST_F(ScudoWrappersCTest,Calloc)18988852964SChia-hung Duan TEST_F(ScudoWrappersCTest, Calloc) {
1903fa38318SNico Weber void *P = calloc(1U, Size);
1913fa38318SNico Weber EXPECT_NE(P, nullptr);
1923fa38318SNico Weber EXPECT_LE(Size, malloc_usable_size(P));
19388852964SChia-hung Duan verifyAllocHookPtr(P);
19488852964SChia-hung Duan verifyAllocHookSize(Size);
1953fa38318SNico Weber for (size_t I = 0; I < Size; I++)
1963fa38318SNico Weber EXPECT_EQ((reinterpret_cast<uint8_t *>(P))[I], 0U);
1973fa38318SNico Weber free(P);
19888852964SChia-hung Duan verifyDeallocHookPtr(P);
1993fa38318SNico Weber
2003fa38318SNico Weber P = calloc(1U, 0U);
2013fa38318SNico Weber EXPECT_NE(P, nullptr);
2023fa38318SNico Weber free(P);
2033fa38318SNico Weber P = calloc(0U, 1U);
2043fa38318SNico Weber EXPECT_NE(P, nullptr);
2053fa38318SNico Weber free(P);
2063fa38318SNico Weber
2073fa38318SNico Weber errno = 0;
2083fa38318SNico Weber EXPECT_EQ(calloc(SIZE_MAX, 1U), nullptr);
2093fa38318SNico Weber EXPECT_EQ(errno, ENOMEM);
2103fa38318SNico Weber errno = 0;
2113fa38318SNico Weber EXPECT_EQ(calloc(static_cast<size_t>(LONG_MAX) + 1U, 2U), nullptr);
2123fa38318SNico Weber if (SCUDO_ANDROID)
2133fa38318SNico Weber EXPECT_EQ(errno, ENOMEM);
2143fa38318SNico Weber errno = 0;
2153fa38318SNico Weber EXPECT_EQ(calloc(SIZE_MAX, SIZE_MAX), nullptr);
2163fa38318SNico Weber EXPECT_EQ(errno, ENOMEM);
2173fa38318SNico Weber }
2183fa38318SNico Weber
TEST_F(ScudoWrappersCTest,SmallAlign)21988852964SChia-hung Duan TEST_F(ScudoWrappersCTest, SmallAlign) {
22001c02abcSRiley // Allocating pointers by the powers of 2 from 1 to 0x10000
22101c02abcSRiley // Using powers of 2 due to memalign using powers of 2 and test more sizes
22201c02abcSRiley constexpr size_t MaxSize = 0x10000;
22301c02abcSRiley std::vector<void *> ptrs;
22401c02abcSRiley // Reserving space to prevent further allocation during the test
22501c02abcSRiley ptrs.reserve((scudo::getLeastSignificantSetBitIndex(MaxSize) + 1) *
22601c02abcSRiley (scudo::getLeastSignificantSetBitIndex(MaxSize) + 1) * 3);
22701c02abcSRiley for (size_t Size = 1; Size <= MaxSize; Size <<= 1) {
22801c02abcSRiley for (size_t Align = 1; Align <= MaxSize; Align <<= 1) {
229e78b64dfSMitch Phillips for (size_t Count = 0; Count < 3; ++Count) {
23001c02abcSRiley void *P = memalign(Align, Size);
231e78b64dfSMitch Phillips EXPECT_TRUE(reinterpret_cast<uintptr_t>(P) % Align == 0);
23201c02abcSRiley ptrs.push_back(P);
233e78b64dfSMitch Phillips }
234e78b64dfSMitch Phillips }
235e78b64dfSMitch Phillips }
23601c02abcSRiley for (void *ptr : ptrs)
23701c02abcSRiley free(ptr);
238e78b64dfSMitch Phillips }
239e78b64dfSMitch Phillips
TEST_F(ScudoWrappersCTest,Memalign)24088852964SChia-hung Duan TEST_F(ScudoWrappersCTest, Memalign) {
2413fa38318SNico Weber void *P;
2423fa38318SNico Weber for (size_t I = FIRST_32_SECOND_64(2U, 3U); I <= 18U; I++) {
2433fa38318SNico Weber const size_t Alignment = 1U << I;
2443fa38318SNico Weber
2453fa38318SNico Weber P = memalign(Alignment, Size);
2463fa38318SNico Weber EXPECT_NE(P, nullptr);
2473fa38318SNico Weber EXPECT_LE(Size, malloc_usable_size(P));
2483fa38318SNico Weber EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
24988852964SChia-hung Duan verifyAllocHookPtr(P);
25088852964SChia-hung Duan verifyAllocHookSize(Size);
2513fa38318SNico Weber free(P);
25288852964SChia-hung Duan verifyDeallocHookPtr(P);
2533fa38318SNico Weber
2543fa38318SNico Weber P = nullptr;
2553fa38318SNico Weber EXPECT_EQ(posix_memalign(&P, Alignment, Size), 0);
2563fa38318SNico Weber EXPECT_NE(P, nullptr);
2573fa38318SNico Weber EXPECT_LE(Size, malloc_usable_size(P));
2583fa38318SNico Weber EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
25988852964SChia-hung Duan verifyAllocHookPtr(P);
26088852964SChia-hung Duan verifyAllocHookSize(Size);
2613fa38318SNico Weber free(P);
26288852964SChia-hung Duan verifyDeallocHookPtr(P);
2633fa38318SNico Weber }
2643fa38318SNico Weber
2653fa38318SNico Weber EXPECT_EQ(memalign(4096U, SIZE_MAX), nullptr);
2663fa38318SNico Weber EXPECT_EQ(posix_memalign(&P, 15U, Size), EINVAL);
2673fa38318SNico Weber EXPECT_EQ(posix_memalign(&P, 4096U, SIZE_MAX), ENOMEM);
2683fa38318SNico Weber
2693fa38318SNico Weber // Android's memalign accepts non power-of-2 alignments, and 0.
2703fa38318SNico Weber if (SCUDO_ANDROID) {
2713fa38318SNico Weber for (size_t Alignment = 0U; Alignment <= 128U; Alignment++) {
2723fa38318SNico Weber P = memalign(Alignment, 1024U);
2733fa38318SNico Weber EXPECT_NE(P, nullptr);
27488852964SChia-hung Duan verifyAllocHookPtr(P);
27588852964SChia-hung Duan verifyAllocHookSize(Size);
2763fa38318SNico Weber free(P);
27788852964SChia-hung Duan verifyDeallocHookPtr(P);
2783fa38318SNico Weber }
2793fa38318SNico Weber }
2803fa38318SNico Weber }
2813fa38318SNico Weber
TEST_F(ScudoWrappersCTest,AlignedAlloc)28288852964SChia-hung Duan TEST_F(ScudoWrappersCTest, AlignedAlloc) {
2833fa38318SNico Weber const size_t Alignment = 4096U;
2843fa38318SNico Weber void *P = aligned_alloc(Alignment, Alignment * 4U);
2853fa38318SNico Weber EXPECT_NE(P, nullptr);
2863fa38318SNico Weber EXPECT_LE(Alignment * 4U, malloc_usable_size(P));
2873fa38318SNico Weber EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
28888852964SChia-hung Duan verifyAllocHookPtr(P);
28988852964SChia-hung Duan verifyAllocHookSize(Alignment * 4U);
2903fa38318SNico Weber free(P);
29188852964SChia-hung Duan verifyDeallocHookPtr(P);
2923fa38318SNico Weber
2933fa38318SNico Weber errno = 0;
2943fa38318SNico Weber P = aligned_alloc(Alignment, Size);
2953fa38318SNico Weber EXPECT_EQ(P, nullptr);
2963fa38318SNico Weber EXPECT_EQ(errno, EINVAL);
2973fa38318SNico Weber }
2983fa38318SNico Weber
TEST_F(ScudoWrappersCDeathTest,Realloc)29988852964SChia-hung Duan TEST_F(ScudoWrappersCDeathTest, Realloc) {
30075867f8eSChiaHungDuan invalidateHookPtrs();
3013fa38318SNico Weber // realloc(nullptr, N) is malloc(N)
3024f76810dSChia-hung Duan void *P = realloc(nullptr, Size);
3033fa38318SNico Weber EXPECT_NE(P, nullptr);
30488852964SChia-hung Duan verifyAllocHookPtr(P);
30588852964SChia-hung Duan verifyAllocHookSize(Size);
3063fa38318SNico Weber free(P);
30788852964SChia-hung Duan verifyDeallocHookPtr(P);
3083fa38318SNico Weber
30975867f8eSChiaHungDuan invalidateHookPtrs();
3103fa38318SNico Weber P = malloc(Size);
3113fa38318SNico Weber EXPECT_NE(P, nullptr);
3123fa38318SNico Weber // realloc(P, 0U) is free(P) and returns nullptr
3133fa38318SNico Weber EXPECT_EQ(realloc(P, 0U), nullptr);
31488852964SChia-hung Duan verifyDeallocHookPtr(P);
3153fa38318SNico Weber
3163fa38318SNico Weber P = malloc(Size);
3173fa38318SNico Weber EXPECT_NE(P, nullptr);
3183fa38318SNico Weber EXPECT_LE(Size, malloc_usable_size(P));
3193fa38318SNico Weber memset(P, 0x42, Size);
3203fa38318SNico Weber
32175867f8eSChiaHungDuan invalidateHookPtrs();
3224f76810dSChia-hung Duan void *OldP = P;
3233fa38318SNico Weber P = realloc(P, Size * 2U);
3243fa38318SNico Weber EXPECT_NE(P, nullptr);
3253fa38318SNico Weber EXPECT_LE(Size * 2U, malloc_usable_size(P));
3263fa38318SNico Weber for (size_t I = 0; I < Size; I++)
3273fa38318SNico Weber EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
3284f76810dSChia-hung Duan if (OldP == P) {
32975867f8eSChiaHungDuan verifyDeallocHookPtr(OldP);
33075867f8eSChiaHungDuan verifyAllocHookPtr(OldP);
3314f76810dSChia-hung Duan } else {
33288852964SChia-hung Duan verifyAllocHookPtr(P);
33388852964SChia-hung Duan verifyAllocHookSize(Size * 2U);
33488852964SChia-hung Duan verifyDeallocHookPtr(OldP);
3354f76810dSChia-hung Duan }
336*58c2a4e8SChiaHungDuan verifyReallocHookPtrs(OldP, P, Size * 2U);
3373fa38318SNico Weber
33875867f8eSChiaHungDuan invalidateHookPtrs();
3394f76810dSChia-hung Duan OldP = P;
3403fa38318SNico Weber P = realloc(P, Size / 2U);
3413fa38318SNico Weber EXPECT_NE(P, nullptr);
3423fa38318SNico Weber EXPECT_LE(Size / 2U, malloc_usable_size(P));
3433fa38318SNico Weber for (size_t I = 0; I < Size / 2U; I++)
3443fa38318SNico Weber EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
3454f76810dSChia-hung Duan if (OldP == P) {
34675867f8eSChiaHungDuan verifyDeallocHookPtr(OldP);
34775867f8eSChiaHungDuan verifyAllocHookPtr(OldP);
3484f76810dSChia-hung Duan } else {
34988852964SChia-hung Duan verifyAllocHookPtr(P);
35088852964SChia-hung Duan verifyAllocHookSize(Size / 2U);
3514f76810dSChia-hung Duan }
352*58c2a4e8SChiaHungDuan verifyReallocHookPtrs(OldP, P, Size / 2U);
3533fa38318SNico Weber free(P);
3543fa38318SNico Weber
3553fa38318SNico Weber EXPECT_DEATH(P = realloc(P, Size), "");
3563fa38318SNico Weber
3573fa38318SNico Weber errno = 0;
3583fa38318SNico Weber EXPECT_EQ(realloc(nullptr, SIZE_MAX), nullptr);
3593fa38318SNico Weber EXPECT_EQ(errno, ENOMEM);
3603fa38318SNico Weber P = malloc(Size);
3613fa38318SNico Weber EXPECT_NE(P, nullptr);
3623fa38318SNico Weber errno = 0;
3633fa38318SNico Weber EXPECT_EQ(realloc(P, SIZE_MAX), nullptr);
3643fa38318SNico Weber EXPECT_EQ(errno, ENOMEM);
3653fa38318SNico Weber free(P);
3663fa38318SNico Weber
3673fa38318SNico Weber // Android allows realloc of memalign pointers.
3683fa38318SNico Weber if (SCUDO_ANDROID) {
3693fa38318SNico Weber const size_t Alignment = 1024U;
3703fa38318SNico Weber P = memalign(Alignment, Size);
3713fa38318SNico Weber EXPECT_NE(P, nullptr);
3723fa38318SNico Weber EXPECT_LE(Size, malloc_usable_size(P));
3733fa38318SNico Weber EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
3743fa38318SNico Weber memset(P, 0x42, Size);
3753fa38318SNico Weber
3763fa38318SNico Weber P = realloc(P, Size * 2U);
3773fa38318SNico Weber EXPECT_NE(P, nullptr);
3783fa38318SNico Weber EXPECT_LE(Size * 2U, malloc_usable_size(P));
3793fa38318SNico Weber for (size_t I = 0; I < Size; I++)
3803fa38318SNico Weber EXPECT_EQ(0x42, (reinterpret_cast<uint8_t *>(P))[I]);
3813fa38318SNico Weber free(P);
3823fa38318SNico Weber }
3833fa38318SNico Weber }
3843fa38318SNico Weber
3850d3d4d3bSKostya Kortchinsky #if !SCUDO_FUCHSIA
TEST_F(ScudoWrappersCTest,MallOpt)38688852964SChia-hung Duan TEST_F(ScudoWrappersCTest, MallOpt) {
3873fa38318SNico Weber errno = 0;
3883fa38318SNico Weber EXPECT_EQ(mallopt(-1000, 1), 0);
3893fa38318SNico Weber // mallopt doesn't set errno.
3903fa38318SNico Weber EXPECT_EQ(errno, 0);
3913fa38318SNico Weber
3923fa38318SNico Weber EXPECT_EQ(mallopt(M_PURGE, 0), 1);
3933fa38318SNico Weber
3943fa38318SNico Weber EXPECT_EQ(mallopt(M_DECAY_TIME, 1), 1);
3953fa38318SNico Weber EXPECT_EQ(mallopt(M_DECAY_TIME, 0), 1);
3963fa38318SNico Weber EXPECT_EQ(mallopt(M_DECAY_TIME, 1), 1);
3973fa38318SNico Weber EXPECT_EQ(mallopt(M_DECAY_TIME, 0), 1);
398f14472a2SChristopher Ferris
399f14472a2SChristopher Ferris if (SCUDO_ANDROID) {
400f14472a2SChristopher Ferris EXPECT_EQ(mallopt(M_CACHE_COUNT_MAX, 100), 1);
401f14472a2SChristopher Ferris EXPECT_EQ(mallopt(M_CACHE_SIZE_MAX, 1024 * 1024 * 2), 1);
402f14472a2SChristopher Ferris EXPECT_EQ(mallopt(M_TSDS_COUNT_MAX, 10), 1);
403f14472a2SChristopher Ferris }
4043fa38318SNico Weber }
4050d3d4d3bSKostya Kortchinsky #endif
4063fa38318SNico Weber
TEST_F(ScudoWrappersCTest,OtherAlloc)40788852964SChia-hung Duan TEST_F(ScudoWrappersCTest, OtherAlloc) {
408c981f0b4SChristopher Ferris #if HAVE_PVALLOC
409af41f79fSChristopher Ferris const size_t PageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
4103fa38318SNico Weber
4113fa38318SNico Weber void *P = pvalloc(Size);
4123fa38318SNico Weber EXPECT_NE(P, nullptr);
4133fa38318SNico Weber EXPECT_EQ(reinterpret_cast<uintptr_t>(P) & (PageSize - 1), 0U);
4143fa38318SNico Weber EXPECT_LE(PageSize, malloc_usable_size(P));
41588852964SChia-hung Duan verifyAllocHookPtr(P);
4164f76810dSChia-hung Duan // Size will be rounded up to PageSize.
41788852964SChia-hung Duan verifyAllocHookSize(PageSize);
4183fa38318SNico Weber free(P);
41988852964SChia-hung Duan verifyDeallocHookPtr(P);
4203fa38318SNico Weber
4213fa38318SNico Weber EXPECT_EQ(pvalloc(SIZE_MAX), nullptr);
4223fa38318SNico Weber
4233fa38318SNico Weber P = pvalloc(Size);
4243fa38318SNico Weber EXPECT_NE(P, nullptr);
4253fa38318SNico Weber EXPECT_EQ(reinterpret_cast<uintptr_t>(P) & (PageSize - 1), 0U);
4263fa38318SNico Weber free(P);
4270d3d4d3bSKostya Kortchinsky #endif
4283fa38318SNico Weber
429c981f0b4SChristopher Ferris #if HAVE_VALLOC
4303fa38318SNico Weber EXPECT_EQ(valloc(SIZE_MAX), nullptr);
431c981f0b4SChristopher Ferris #endif
4323fa38318SNico Weber }
4332be59170SKostya Kortchinsky
434fd1721d8SChristopher Ferris template<typename FieldType>
MallInfoTest()435fd1721d8SChristopher Ferris void MallInfoTest() {
43682fc4cc6SVitaly Buka // mallinfo is deprecated.
43782fc4cc6SVitaly Buka #pragma clang diagnostic push
43882fc4cc6SVitaly Buka #pragma clang diagnostic ignored "-Wdeprecated-declarations"
439fd1721d8SChristopher Ferris const FieldType BypassQuarantineSize = 1024U;
4402be59170SKostya Kortchinsky struct mallinfo MI = mallinfo();
441fd1721d8SChristopher Ferris FieldType Allocated = MI.uordblks;
4422be59170SKostya Kortchinsky void *P = malloc(BypassQuarantineSize);
4432be59170SKostya Kortchinsky EXPECT_NE(P, nullptr);
4442be59170SKostya Kortchinsky MI = mallinfo();
445fd1721d8SChristopher Ferris EXPECT_GE(MI.uordblks, Allocated + BypassQuarantineSize);
44679e96b24SMitch Phillips EXPECT_GT(MI.hblkhd, static_cast<FieldType>(0));
447fd1721d8SChristopher Ferris FieldType Free = MI.fordblks;
4482be59170SKostya Kortchinsky free(P);
4492be59170SKostya Kortchinsky MI = mallinfo();
450fd1721d8SChristopher Ferris EXPECT_GE(MI.fordblks, Free + BypassQuarantineSize);
45182fc4cc6SVitaly Buka #pragma clang diagnostic pop
45282fc4cc6SVitaly Buka }
453fd1721d8SChristopher Ferris
454fd1721d8SChristopher Ferris #if !SCUDO_FUCHSIA
TEST_F(ScudoWrappersCTest,MallInfo)455fd1721d8SChristopher Ferris TEST_F(ScudoWrappersCTest, MallInfo) {
456fd1721d8SChristopher Ferris #if SCUDO_ANDROID
457fd1721d8SChristopher Ferris // Android accidentally set the fields to size_t instead of int.
458fd1721d8SChristopher Ferris MallInfoTest<size_t>();
459fd1721d8SChristopher Ferris #else
460fd1721d8SChristopher Ferris MallInfoTest<int>();
461fd1721d8SChristopher Ferris #endif
462fd1721d8SChristopher Ferris }
46382fc4cc6SVitaly Buka #endif
46482fc4cc6SVitaly Buka
465fd1721d8SChristopher Ferris #if __GLIBC_PREREQ(2, 33) || SCUDO_ANDROID
TEST_F(ScudoWrappersCTest,MallInfo2)46688852964SChia-hung Duan TEST_F(ScudoWrappersCTest, MallInfo2) {
46782fc4cc6SVitaly Buka const size_t BypassQuarantineSize = 1024U;
46882fc4cc6SVitaly Buka struct mallinfo2 MI = mallinfo2();
46982fc4cc6SVitaly Buka size_t Allocated = MI.uordblks;
47082fc4cc6SVitaly Buka void *P = malloc(BypassQuarantineSize);
47182fc4cc6SVitaly Buka EXPECT_NE(P, nullptr);
47282fc4cc6SVitaly Buka MI = mallinfo2();
47382fc4cc6SVitaly Buka EXPECT_GE(MI.uordblks, Allocated + BypassQuarantineSize);
47482fc4cc6SVitaly Buka EXPECT_GT(MI.hblkhd, 0U);
47582fc4cc6SVitaly Buka size_t Free = MI.fordblks;
47682fc4cc6SVitaly Buka free(P);
47782fc4cc6SVitaly Buka MI = mallinfo2();
47882fc4cc6SVitaly Buka EXPECT_GE(MI.fordblks, Free + BypassQuarantineSize);
4792be59170SKostya Kortchinsky }
4800d3d4d3bSKostya Kortchinsky #endif
4813e5360f1SKostya Kortchinsky
4823e5360f1SKostya Kortchinsky static uintptr_t BoundaryP;
4833e5360f1SKostya Kortchinsky static size_t Count;
4843e5360f1SKostya Kortchinsky
callback(uintptr_t Base,UNUSED size_t Size,UNUSED void * Arg)485af41f79fSChristopher Ferris static void callback(uintptr_t Base, UNUSED size_t Size, UNUSED void *Arg) {
4861e6d1353SVitaly Buka if (scudo::archSupportsMemoryTagging()) {
4871e6d1353SVitaly Buka Base = scudo::untagPointer(Base);
4881e6d1353SVitaly Buka BoundaryP = scudo::untagPointer(BoundaryP);
4891e6d1353SVitaly Buka }
4903e5360f1SKostya Kortchinsky if (Base == BoundaryP)
4913e5360f1SKostya Kortchinsky Count++;
4923e5360f1SKostya Kortchinsky }
4933e5360f1SKostya Kortchinsky
4943e5360f1SKostya Kortchinsky // Verify that a block located on an iteration boundary is not mis-accounted.
4953e5360f1SKostya Kortchinsky // To achieve this, we allocate a chunk for which the backing block will be
4963e5360f1SKostya Kortchinsky // aligned on a page, then run the malloc_iterate on both the pages that the
4973e5360f1SKostya Kortchinsky // block is a boundary for. It must only be seen once by the callback function.
TEST_F(ScudoWrappersCTest,MallocIterateBoundary)49888852964SChia-hung Duan TEST_F(ScudoWrappersCTest, MallocIterateBoundary) {
499af41f79fSChristopher Ferris const size_t PageSize = static_cast<size_t>(sysconf(_SC_PAGESIZE));
500261d9e58SChristopher Ferris #if SCUDO_ANDROID
501261d9e58SChristopher Ferris // Android uses a 16 byte alignment for both 32 bit and 64 bit.
502261d9e58SChristopher Ferris const size_t BlockDelta = 16U;
503261d9e58SChristopher Ferris #else
5046ee594beSChristopher Ferris const size_t BlockDelta = FIRST_32_SECOND_64(8U, 16U);
505261d9e58SChristopher Ferris #endif
5063e5360f1SKostya Kortchinsky const size_t SpecialSize = PageSize - BlockDelta;
5073e5360f1SKostya Kortchinsky
50887303fd9SPeter Collingbourne // We aren't guaranteed that any size class is exactly a page wide. So we need
509261d9e58SChristopher Ferris // to keep making allocations until we get an allocation that starts exactly
510261d9e58SChristopher Ferris // on a page boundary. The BlockDelta value is expected to be the number of
511261d9e58SChristopher Ferris // bytes to subtract from a returned pointer to get to the actual start of
512261d9e58SChristopher Ferris // the pointer in the size class. In practice, this means BlockDelta should
513261d9e58SChristopher Ferris // be set to the minimum alignment in bytes for the allocation.
51487303fd9SPeter Collingbourne //
51587303fd9SPeter Collingbourne // With a 16-byte block alignment and 4096-byte page size, each allocation has
51687303fd9SPeter Collingbourne // a probability of (1 - (16/4096)) of failing to meet the alignment
51787303fd9SPeter Collingbourne // requirements, and the probability of failing 65536 times is
51887303fd9SPeter Collingbourne // (1 - (16/4096))^65536 < 10^-112. So if we still haven't succeeded after
51987303fd9SPeter Collingbourne // 65536 tries, give up.
52087303fd9SPeter Collingbourne uintptr_t Block;
52187303fd9SPeter Collingbourne void *P = nullptr;
52287303fd9SPeter Collingbourne for (unsigned I = 0; I != 65536; ++I) {
52387303fd9SPeter Collingbourne void *PrevP = P;
52487303fd9SPeter Collingbourne P = malloc(SpecialSize);
5253e5360f1SKostya Kortchinsky EXPECT_NE(P, nullptr);
52687303fd9SPeter Collingbourne *reinterpret_cast<void **>(P) = PrevP;
5273e5360f1SKostya Kortchinsky BoundaryP = reinterpret_cast<uintptr_t>(P);
52887303fd9SPeter Collingbourne Block = BoundaryP - BlockDelta;
52987303fd9SPeter Collingbourne if ((Block & (PageSize - 1)) == 0U)
53087303fd9SPeter Collingbourne break;
53187303fd9SPeter Collingbourne }
5323e5360f1SKostya Kortchinsky EXPECT_EQ((Block & (PageSize - 1)), 0U);
5333e5360f1SKostya Kortchinsky
5343e5360f1SKostya Kortchinsky Count = 0U;
5353e5360f1SKostya Kortchinsky malloc_disable();
5363e5360f1SKostya Kortchinsky malloc_iterate(Block - PageSize, PageSize, callback, nullptr);
5373e5360f1SKostya Kortchinsky malloc_iterate(Block, PageSize, callback, nullptr);
5383e5360f1SKostya Kortchinsky malloc_enable();
5393e5360f1SKostya Kortchinsky EXPECT_EQ(Count, 1U);
5403e5360f1SKostya Kortchinsky
54187303fd9SPeter Collingbourne while (P) {
54287303fd9SPeter Collingbourne void *NextP = *reinterpret_cast<void **>(P);
5433e5360f1SKostya Kortchinsky free(P);
54487303fd9SPeter Collingbourne P = NextP;
54587303fd9SPeter Collingbourne }
5463e5360f1SKostya Kortchinsky }
547dc802dbeSKostya Kortchinsky
548e9cc5fefSKostya Kortchinsky // Fuchsia doesn't have alarm, fork or malloc_info.
549e9cc5fefSKostya Kortchinsky #if !SCUDO_FUCHSIA
TEST_F(ScudoWrappersCDeathTest,MallocDisableDeadlock)55088852964SChia-hung Duan TEST_F(ScudoWrappersCDeathTest, MallocDisableDeadlock) {
551e9cc5fefSKostya Kortchinsky // We expect heap operations within a disable/enable scope to deadlock.
55277e906acSKostya Kortchinsky EXPECT_DEATH(
55377e906acSKostya Kortchinsky {
55477e906acSKostya Kortchinsky void *P = malloc(Size);
55577e906acSKostya Kortchinsky EXPECT_NE(P, nullptr);
55677e906acSKostya Kortchinsky free(P);
55777e906acSKostya Kortchinsky malloc_disable();
55877e906acSKostya Kortchinsky alarm(1);
55977e906acSKostya Kortchinsky P = malloc(Size);
56077e906acSKostya Kortchinsky malloc_enable();
56177e906acSKostya Kortchinsky },
56277e906acSKostya Kortchinsky "");
56377e906acSKostya Kortchinsky }
56477e906acSKostya Kortchinsky
TEST_F(ScudoWrappersCTest,MallocInfo)56588852964SChia-hung Duan TEST_F(ScudoWrappersCTest, MallocInfo) {
5669068766bSPeter Collingbourne // Use volatile so that the allocations don't get optimized away.
5679068766bSPeter Collingbourne void *volatile P1 = malloc(1234);
5689068766bSPeter Collingbourne void *volatile P2 = malloc(4321);
5699068766bSPeter Collingbourne
5709068766bSPeter Collingbourne char Buffer[16384];
571dc802dbeSKostya Kortchinsky FILE *F = fmemopen(Buffer, sizeof(Buffer), "w+");
572dc802dbeSKostya Kortchinsky EXPECT_NE(F, nullptr);
573dc802dbeSKostya Kortchinsky errno = 0;
574dc802dbeSKostya Kortchinsky EXPECT_EQ(malloc_info(0, F), 0);
575dc802dbeSKostya Kortchinsky EXPECT_EQ(errno, 0);
576dc802dbeSKostya Kortchinsky fclose(F);
577dc802dbeSKostya Kortchinsky EXPECT_EQ(strncmp(Buffer, "<malloc version=\"scudo-", 23), 0);
5789068766bSPeter Collingbourne EXPECT_NE(nullptr, strstr(Buffer, "<alloc size=\"1234\" count=\""));
5799068766bSPeter Collingbourne EXPECT_NE(nullptr, strstr(Buffer, "<alloc size=\"4321\" count=\""));
5809068766bSPeter Collingbourne
5819068766bSPeter Collingbourne free(P1);
5829068766bSPeter Collingbourne free(P2);
583dc802dbeSKostya Kortchinsky }
5849ef6faf4SKostya Kortchinsky
TEST_F(ScudoWrappersCDeathTest,Fork)58588852964SChia-hung Duan TEST_F(ScudoWrappersCDeathTest, Fork) {
5869ef6faf4SKostya Kortchinsky void *P;
5879ef6faf4SKostya Kortchinsky pid_t Pid = fork();
588b41b76b3SVitaly Buka EXPECT_GE(Pid, 0) << strerror(errno);
5899ef6faf4SKostya Kortchinsky if (Pid == 0) {
5909ef6faf4SKostya Kortchinsky P = malloc(Size);
5919ef6faf4SKostya Kortchinsky EXPECT_NE(P, nullptr);
5929ef6faf4SKostya Kortchinsky memset(P, 0x42, Size);
5939ef6faf4SKostya Kortchinsky free(P);
5949ef6faf4SKostya Kortchinsky _exit(0);
5959ef6faf4SKostya Kortchinsky }
5969ef6faf4SKostya Kortchinsky waitpid(Pid, nullptr, 0);
5979ef6faf4SKostya Kortchinsky P = malloc(Size);
5989ef6faf4SKostya Kortchinsky EXPECT_NE(P, nullptr);
5999ef6faf4SKostya Kortchinsky memset(P, 0x42, Size);
6009ef6faf4SKostya Kortchinsky free(P);
6019ef6faf4SKostya Kortchinsky
6029ef6faf4SKostya Kortchinsky // fork should stall if the allocator has been disabled.
6039ef6faf4SKostya Kortchinsky EXPECT_DEATH(
6049ef6faf4SKostya Kortchinsky {
6059ef6faf4SKostya Kortchinsky malloc_disable();
6069ef6faf4SKostya Kortchinsky alarm(1);
6079ef6faf4SKostya Kortchinsky Pid = fork();
6089ef6faf4SKostya Kortchinsky EXPECT_GE(Pid, 0);
6099ef6faf4SKostya Kortchinsky },
6109ef6faf4SKostya Kortchinsky "");
6119ef6faf4SKostya Kortchinsky }
6129ef6faf4SKostya Kortchinsky
6139ef6faf4SKostya Kortchinsky static pthread_mutex_t Mutex;
6149ef6faf4SKostya Kortchinsky static pthread_cond_t Conditional = PTHREAD_COND_INITIALIZER;
615519959adSEvgenii Stepanov static bool Ready;
6169ef6faf4SKostya Kortchinsky
enableMalloc(UNUSED void * Unused)617af41f79fSChristopher Ferris static void *enableMalloc(UNUSED void *Unused) {
6189ef6faf4SKostya Kortchinsky // Initialize the allocator for this thread.
6199ef6faf4SKostya Kortchinsky void *P = malloc(Size);
6209ef6faf4SKostya Kortchinsky EXPECT_NE(P, nullptr);
6219ef6faf4SKostya Kortchinsky memset(P, 0x42, Size);
6229ef6faf4SKostya Kortchinsky free(P);
6239ef6faf4SKostya Kortchinsky
6249ef6faf4SKostya Kortchinsky // Signal the main thread we are ready.
6259ef6faf4SKostya Kortchinsky pthread_mutex_lock(&Mutex);
626519959adSEvgenii Stepanov Ready = true;
6279ef6faf4SKostya Kortchinsky pthread_cond_signal(&Conditional);
6289ef6faf4SKostya Kortchinsky pthread_mutex_unlock(&Mutex);
6299ef6faf4SKostya Kortchinsky
6309ef6faf4SKostya Kortchinsky // Wait for the malloc_disable & fork, then enable the allocator again.
6319ef6faf4SKostya Kortchinsky sleep(1);
6329ef6faf4SKostya Kortchinsky malloc_enable();
6339ef6faf4SKostya Kortchinsky
6349ef6faf4SKostya Kortchinsky return nullptr;
6359ef6faf4SKostya Kortchinsky }
6369ef6faf4SKostya Kortchinsky
TEST_F(ScudoWrappersCTest,DisableForkEnable)63788852964SChia-hung Duan TEST_F(ScudoWrappersCTest, DisableForkEnable) {
6389ef6faf4SKostya Kortchinsky pthread_t ThreadId;
6396f00f3b5SKostya Kortchinsky Ready = false;
6409ef6faf4SKostya Kortchinsky EXPECT_EQ(pthread_create(&ThreadId, nullptr, &enableMalloc, nullptr), 0);
6419ef6faf4SKostya Kortchinsky
6429ef6faf4SKostya Kortchinsky // Wait for the thread to be warmed up.
6439ef6faf4SKostya Kortchinsky pthread_mutex_lock(&Mutex);
644519959adSEvgenii Stepanov while (!Ready)
6459ef6faf4SKostya Kortchinsky pthread_cond_wait(&Conditional, &Mutex);
6469ef6faf4SKostya Kortchinsky pthread_mutex_unlock(&Mutex);
6479ef6faf4SKostya Kortchinsky
6489ef6faf4SKostya Kortchinsky // Disable the allocator and fork. fork should succeed after malloc_enable.
6499ef6faf4SKostya Kortchinsky malloc_disable();
6509ef6faf4SKostya Kortchinsky pid_t Pid = fork();
6519ef6faf4SKostya Kortchinsky EXPECT_GE(Pid, 0);
6529ef6faf4SKostya Kortchinsky if (Pid == 0) {
6539ef6faf4SKostya Kortchinsky void *P = malloc(Size);
6549ef6faf4SKostya Kortchinsky EXPECT_NE(P, nullptr);
6559ef6faf4SKostya Kortchinsky memset(P, 0x42, Size);
6569ef6faf4SKostya Kortchinsky free(P);
6579ef6faf4SKostya Kortchinsky _exit(0);
6589ef6faf4SKostya Kortchinsky }
6599ef6faf4SKostya Kortchinsky waitpid(Pid, nullptr, 0);
6609ef6faf4SKostya Kortchinsky EXPECT_EQ(pthread_join(ThreadId, 0), 0);
6619ef6faf4SKostya Kortchinsky }
6629ef6faf4SKostya Kortchinsky
6639ef6faf4SKostya Kortchinsky #endif // SCUDO_FUCHSIA
664