Merge "Revert^2 "memtag_stack tests.""
This commit is contained in:
commit
44742701c9
4 changed files with 382 additions and 0 deletions
|
@ -393,6 +393,7 @@ cc_test_library {
|
|||
"math_test.cpp",
|
||||
"math_force_long_double_test.cpp",
|
||||
"membarrier_test.cpp",
|
||||
"memtag_stack_test.cpp",
|
||||
"mntent_test.cpp",
|
||||
"mte_test.cpp",
|
||||
"netdb_test.cpp",
|
||||
|
@ -913,6 +914,8 @@ cc_defaults {
|
|||
"heap_tagging_static_disabled_helper",
|
||||
"heap_tagging_static_sync_helper",
|
||||
"heap_tagging_sync_helper",
|
||||
"stack_tagging_helper",
|
||||
"stack_tagging_static_helper",
|
||||
"ld_config_test_helper",
|
||||
"ld_config_test_helper_lib1",
|
||||
"ld_config_test_helper_lib2",
|
||||
|
@ -1161,6 +1164,8 @@ cc_test {
|
|||
"heap_tagging_static_disabled_helper",
|
||||
"heap_tagging_static_sync_helper",
|
||||
"heap_tagging_sync_helper",
|
||||
"stack_tagging_helper",
|
||||
"stack_tagging_static_helper",
|
||||
],
|
||||
}
|
||||
|
||||
|
|
|
@ -1652,6 +1652,33 @@ cc_test {
|
|||
},
|
||||
}
|
||||
|
||||
cc_test {
|
||||
name: "stack_tagging_helper",
|
||||
defaults: ["bionic_testlib_defaults", "bionic_targets_only"],
|
||||
srcs: ["stack_tagging_helper.cpp"],
|
||||
sanitize: {
|
||||
memtag_heap: true,
|
||||
memtag_stack: true,
|
||||
diag: {
|
||||
memtag_heap: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cc_test {
|
||||
name: "stack_tagging_static_helper",
|
||||
defaults: ["bionic_testlib_defaults", "bionic_targets_only"],
|
||||
srcs: ["stack_tagging_helper.cpp"],
|
||||
static_executable: true,
|
||||
sanitize: {
|
||||
memtag_heap: true,
|
||||
memtag_stack: true,
|
||||
diag: {
|
||||
memtag_heap: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
cc_genrule {
|
||||
name: "libdlext_test_zip_zipaligned",
|
||||
out: ["bionic-loader-test-libs/libdlext_test_zip/libdlext_test_zip_zipaligned.zip"],
|
||||
|
|
294
tests/libs/stack_tagging_helper.cpp
Normal file
294
tests/libs/stack_tagging_helper.cpp
Normal file
|
@ -0,0 +1,294 @@
|
|||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <thread>
|
||||
|
||||
#include "libs_utils.h"
|
||||
|
||||
#if defined(__aarch64__)
|
||||
|
||||
template <typename T>
|
||||
static inline void mte_set_tag(T* p) {
|
||||
__asm__ __volatile__(
|
||||
".arch_extension memtag\n"
|
||||
"stg %[Ptr], [%[Ptr]]\n"
|
||||
:
|
||||
: [Ptr] "r"(p)
|
||||
: "memory");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T* mte_get_tag(T* p) {
|
||||
__asm__ __volatile__(
|
||||
".arch_extension memtag\n"
|
||||
"ldg %[Ptr], [%[Ptr]]\n"
|
||||
: [Ptr] "+r"(p)
|
||||
:
|
||||
: "memory");
|
||||
return p;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static inline T* mte_increment_tag(T* p) {
|
||||
T* res;
|
||||
__asm__ __volatile__(
|
||||
".arch_extension memtag\n"
|
||||
"addg %[Res], %[Ptr], #0, #1\n"
|
||||
: [Res] "=r"(res)
|
||||
: [Ptr] "r"(p)
|
||||
: "memory");
|
||||
return res;
|
||||
}
|
||||
|
||||
constexpr size_t kStackAllocationSize = 128 * 1024;
|
||||
|
||||
// Prevent optimizations.
|
||||
volatile void* sink;
|
||||
|
||||
enum struct ChildAction { Exit, Execve, Execl };
|
||||
|
||||
// Either execve or _exit, transferring control back to parent.
|
||||
__attribute__((no_sanitize("memtag"), optnone, noinline)) void vfork_child2(ChildAction action,
|
||||
void* fp_parent) {
|
||||
// Make sure that the buffer in the caller has not been optimized out.
|
||||
void* fp = __builtin_frame_address(0);
|
||||
CHECK(reinterpret_cast<uintptr_t>(fp_parent) - reinterpret_cast<uintptr_t>(fp) >=
|
||||
kStackAllocationSize);
|
||||
if (action == ChildAction::Execve) {
|
||||
const char* argv[] = {"/system/bin/true", nullptr};
|
||||
const char* envp[] = {nullptr};
|
||||
execve("/system/bin/true", const_cast<char**>(argv), const_cast<char**>(envp));
|
||||
fprintf(stderr, "execve failed: %m\n");
|
||||
_exit(1);
|
||||
} else if (action == ChildAction::Execl) {
|
||||
execl("/system/bin/true", "/system/bin/true", "unusedA", "unusedB", nullptr);
|
||||
fprintf(stderr, "execl failed: %m\n");
|
||||
_exit(1);
|
||||
} else if (action == ChildAction::Exit) {
|
||||
_exit(0);
|
||||
}
|
||||
CHECK(0);
|
||||
}
|
||||
|
||||
// Place a tagged buffer on the stack. Do not tag the top half so that the parent does not crash too
|
||||
// early even if things go wrong.
|
||||
__attribute__((no_sanitize("memtag"), optnone, noinline)) void vfork_child(ChildAction action) {
|
||||
alignas(16) char buf[kStackAllocationSize] __attribute__((uninitialized));
|
||||
sink = &buf;
|
||||
|
||||
for (char* p = buf; p < buf + sizeof(buf) / 2; p += 16) {
|
||||
char* q = mte_increment_tag(p);
|
||||
mte_set_tag(q);
|
||||
CHECK(mte_get_tag(p) == q);
|
||||
}
|
||||
vfork_child2(action, __builtin_frame_address(0));
|
||||
}
|
||||
|
||||
// Parent. Check that the stack has correct allocation tags.
|
||||
__attribute__((no_sanitize("memtag"), optnone, noinline)) void vfork_parent(pid_t pid) {
|
||||
alignas(16) char buf[kStackAllocationSize] __attribute__((uninitialized));
|
||||
fprintf(stderr, "vfork_parent %p\n", &buf);
|
||||
bool success = true;
|
||||
for (char* p = buf; p < buf + sizeof(buf); p += 16) {
|
||||
char* q = mte_get_tag(p);
|
||||
if (p != q) {
|
||||
fprintf(stderr, "tag mismatch at offset %zx: %p != %p\n", p - buf, p, q);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int wstatus;
|
||||
do {
|
||||
int res = waitpid(pid, &wstatus, 0);
|
||||
CHECK(res == pid);
|
||||
} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
|
||||
|
||||
CHECK(WIFEXITED(wstatus));
|
||||
CHECK(WEXITSTATUS(wstatus) == 0);
|
||||
|
||||
if (!success) exit(1);
|
||||
}
|
||||
|
||||
void test_vfork(ChildAction action) {
|
||||
pid_t pid = vfork();
|
||||
if (pid == 0) {
|
||||
vfork_child(action);
|
||||
} else {
|
||||
vfork_parent(pid);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((no_sanitize("memtag"), optnone, noinline)) static void settag_and_longjmp(
|
||||
jmp_buf cont) {
|
||||
alignas(16) char buf[kStackAllocationSize] __attribute__((uninitialized));
|
||||
sink = &buf;
|
||||
|
||||
for (char* p = buf; p < buf + sizeof(buf) / 2; p += 16) {
|
||||
char* q = mte_increment_tag(p);
|
||||
mte_set_tag(q);
|
||||
if (mte_get_tag(p) != q) {
|
||||
fprintf(stderr, "failed to set allocation tags on stack: %p != %p\n", mte_get_tag(p), q);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
longjmp(cont, 42);
|
||||
}
|
||||
|
||||
// Check that the stack has correct allocation tags.
|
||||
__attribute__((no_sanitize("memtag"), optnone, noinline)) static void check_stack_tags() {
|
||||
alignas(16) char buf[kStackAllocationSize] __attribute__((uninitialized));
|
||||
for (char* p = buf; p < buf + sizeof(buf); p += 16) {
|
||||
void* q = mte_get_tag(p);
|
||||
if (p != q) {
|
||||
fprintf(stderr, "stack tags mismatch: expected %p, got %p", p, q);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void check_longjmp_restores_tags() {
|
||||
int value;
|
||||
jmp_buf jb;
|
||||
if ((value = setjmp(jb)) == 0) {
|
||||
settag_and_longjmp(jb);
|
||||
exit(2); // Unreachable.
|
||||
} else {
|
||||
CHECK(value == 42);
|
||||
check_stack_tags();
|
||||
}
|
||||
}
|
||||
|
||||
class SigAltStackScoped {
|
||||
stack_t old_ss;
|
||||
void* altstack_start;
|
||||
size_t altstack_size;
|
||||
|
||||
public:
|
||||
SigAltStackScoped(size_t sz) : altstack_size(sz) {
|
||||
altstack_start = mmap(nullptr, altstack_size, PROT_READ | PROT_WRITE | PROT_MTE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
|
||||
if (altstack_start == MAP_FAILED) {
|
||||
fprintf(stderr, "sigaltstack mmap failed: %m\n");
|
||||
exit(1);
|
||||
}
|
||||
stack_t ss = {};
|
||||
ss.ss_sp = altstack_start;
|
||||
ss.ss_size = altstack_size;
|
||||
int res = sigaltstack(&ss, &old_ss);
|
||||
CHECK(res == 0);
|
||||
}
|
||||
|
||||
~SigAltStackScoped() {
|
||||
int res = sigaltstack(&old_ss, nullptr);
|
||||
CHECK(res == 0);
|
||||
munmap(altstack_start, altstack_size);
|
||||
}
|
||||
};
|
||||
|
||||
class SigActionScoped {
|
||||
int signo;
|
||||
struct sigaction oldsa;
|
||||
|
||||
public:
|
||||
using handler_t = void (*)(int, siginfo_t* siginfo, void*);
|
||||
|
||||
SigActionScoped(int signo, handler_t handler) : signo(signo) {
|
||||
struct sigaction sa = {};
|
||||
sa.sa_sigaction = handler;
|
||||
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
|
||||
int res = sigaction(signo, &sa, &oldsa);
|
||||
CHECK(res == 0);
|
||||
}
|
||||
|
||||
~SigActionScoped() {
|
||||
int res = sigaction(signo, &oldsa, nullptr);
|
||||
CHECK(res == 0);
|
||||
}
|
||||
};
|
||||
|
||||
void test_longjmp() {
|
||||
check_longjmp_restores_tags();
|
||||
|
||||
std::thread t([]() { check_longjmp_restores_tags(); });
|
||||
t.join();
|
||||
}
|
||||
|
||||
void test_longjmp_sigaltstack() {
|
||||
constexpr size_t kAltStackSize = kStackAllocationSize + PAGE_SIZE * 16;
|
||||
SigAltStackScoped sigAltStackScoped(kAltStackSize);
|
||||
SigActionScoped sigActionScoped(
|
||||
SIGUSR1, [](int, siginfo_t*, void*) { check_longjmp_restores_tags(); });
|
||||
raise(SIGUSR1);
|
||||
|
||||
// same for a secondary thread
|
||||
std::thread t([]() {
|
||||
SigAltStackScoped sigAltStackScoped(kAltStackSize);
|
||||
raise(SIGUSR1);
|
||||
});
|
||||
t.join();
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
printf("nothing to do\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "vfork_execve") == 0) {
|
||||
test_vfork(ChildAction::Execve);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "vfork_execl") == 0) {
|
||||
test_vfork(ChildAction::Execl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "vfork_exit") == 0) {
|
||||
test_vfork(ChildAction::Exit);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "longjmp") == 0) {
|
||||
test_longjmp();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "longjmp_sigaltstack") == 0) {
|
||||
test_longjmp_sigaltstack();
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("unrecognized command: %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
int main(int, char**) {
|
||||
printf("aarch64 only\n");
|
||||
return 1;
|
||||
}
|
||||
#endif // defined(__aarch64__)
|
56
tests/memtag_stack_test.cpp
Normal file
56
tests/memtag_stack_test.cpp
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#if defined(__BIONIC__)
|
||||
#include "gtest_globals.h"
|
||||
#include "platform/bionic/mte.h"
|
||||
#include "utils.h"
|
||||
#endif
|
||||
|
||||
class MemtagStackTest : public testing::TestWithParam<std::tuple<const char*, bool>> {};
|
||||
|
||||
TEST_P(MemtagStackTest, test) {
|
||||
#if defined(__BIONIC__) && defined(__aarch64__)
|
||||
if (!mte_supported()) {
|
||||
return;
|
||||
}
|
||||
bool is_static = std::get<1>(GetParam());
|
||||
std::string helper =
|
||||
GetTestlibRoot() + (is_static ? "/stack_tagging_static_helper" : "/stack_tagging_helper");
|
||||
const char* arg = std::get<0>(GetParam());
|
||||
chmod(helper.c_str(), 0755);
|
||||
ExecTestHelper eth;
|
||||
eth.SetArgs({helper.c_str(), arg, nullptr});
|
||||
eth.Run([&]() { execve(helper.c_str(), eth.GetArgs(), eth.GetEnv()); }, 0, "");
|
||||
#else
|
||||
GTEST_SKIP() << "bionic/arm64 only";
|
||||
#endif
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(, MemtagStackTest,
|
||||
testing::Combine(testing::Values("vfork_execve", "vfork_execl",
|
||||
"vfork_exit", "longjmp",
|
||||
"longjmp_sigaltstack"),
|
||||
testing::Bool()),
|
||||
[](const ::testing::TestParamInfo<MemtagStackTest::ParamType>& info) {
|
||||
std::string s = std::get<0>(info.param);
|
||||
if (std::get<1>(info.param)) s += "_static";
|
||||
return s;
|
||||
});
|
Loading…
Reference in a new issue