[kernel] Test that syscalls taking tagged addresses return errors
Bug: 81499
Change-Id: I47175b39ba56eb2838d4bf8ad59a6a1d995ce10e
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/629452
Reviewed-by: Roland McGrath <mcgrathr@google.com>
Commit-Queue: Leonard Chan <leonardchan@google.com>
diff --git a/zircon/system/utest/core/BUILD.gn b/zircon/system/utest/core/BUILD.gn
index 04758d4..23cd201 100644
--- a/zircon/system/utest/core/BUILD.gn
+++ b/zircon/system/utest/core/BUILD.gn
@@ -10,7 +10,6 @@
# Each subdirectory just provides an eponymous source_set().
core_tests = [
- "address-tagging",
"bad-segsel",
"c11-condvar",
"c11-mutex",
@@ -70,6 +69,7 @@
# component even with additional configuration.
bootfs_only = [
# These tests require ZX_POL_NEW_PROCESS for many tests.
+ "address-tagging",
"job",
"object-info",
"process",
diff --git a/zircon/system/utest/core/address-tagging/BUILD.gn b/zircon/system/utest/core/address-tagging/BUILD.gn
index 3747dd11..2163796 100644
--- a/zircon/system/utest/core/address-tagging/BUILD.gn
+++ b/zircon/system/utest/core/address-tagging/BUILD.gn
@@ -8,6 +8,7 @@
deps = [
"//zircon/kernel/lib/arch",
"//zircon/system/ulib/elf-psabi",
+ "//zircon/system/ulib/mini-process",
"//zircon/system/ulib/test-utils",
"//zircon/system/ulib/zircon-internal",
"//zircon/system/ulib/zxtest",
diff --git a/zircon/system/utest/core/address-tagging/address-tagging-test.cc b/zircon/system/utest/core/address-tagging/address-tagging-test.cc
index 8cdbef4..0818ae1 100644
--- a/zircon/system/utest/core/address-tagging/address-tagging-test.cc
+++ b/zircon/system/utest/core/address-tagging/address-tagging-test.cc
@@ -7,6 +7,7 @@
#include <lib/fit/defer.h>
#include <lib/zircon-internal/default_stack_size.h>
#include <lib/zx/exception.h>
+#include <lib/zx/job.h>
#include <lib/zx/process.h>
#include <lib/zx/thread.h>
#include <zircon/features.h>
@@ -14,6 +15,9 @@
#include <zircon/syscalls.h>
#include <zircon/threads.h>
+#include <algorithm>
+
+#include <mini-process/mini-process.h>
#include <test-utils/test-utils.h>
#include <zxtest/zxtest.h>
@@ -23,6 +27,7 @@
constexpr size_t kTagShift = 56;
constexpr uint8_t kTestTag = 0xAB;
+constexpr size_t kThreadStackSize = ZIRCON_DEFAULT_STACK_SIZE;
constexpr uint64_t AddTag(uintptr_t ptr, uint8_t tag) {
constexpr uint64_t kTagMask = UINT64_C(0xff) << kTagShift;
@@ -137,6 +142,79 @@
ASSERT_OK(exc.set_property(ZX_PROP_EXCEPTION_STATE, &kExceptionState, sizeof(kExceptionState)));
}
+TEST(TopByteIgnoreTests, VmarTaggedAddress) {
+ // Write pattern via VMO and read it via zx_process_read_memory(). Each address argument in these
+ // syscalls must not be tagged, but user pointers can be tagged.
+ uint8_t buff[] = {1, 2, 3, 4};
+ constexpr size_t kVmoSize = sizeof(buff);
+ constexpr size_t kVmarSize = 4096; // Must be page-aligned.
+ constexpr zx_vm_option_t kVmarOpts = ZX_VM_CAN_MAP_READ | ZX_VM_CAN_MAP_WRITE;
+ constexpr zx_vm_option_t kMapOpts = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
+
+ // Setup the VMO. User pointers provided to syscalls can be tagged and work properly.
+ zx::vmo vmo;
+ zx::vmar vmar;
+ zx_vaddr_t vmar_addr, map_addr;
+ ASSERT_OK(zx_vmo_create(kVmoSize, 0u, AddTag(vmo.reset_and_get_address(), kTestTag)));
+ ASSERT_OK(zx_vmar_allocate(zx_vmar_root_self(), kVmarOpts, 0u, kVmarSize,
+ AddTag(vmar.reset_and_get_address(), kTestTag),
+ AddTag(&vmar_addr, kTestTag)));
+ ASSERT_OK(vmar.map(kMapOpts, 0u, vmo, 0u, kVmoSize, AddTag(&map_addr, kTestTag)));
+
+ // Note that the mapopts were set when mapping meaning this would be a no-op,
+ // but this just checks we can't tag vmar_protect regardless.
+ ASSERT_STATUS(vmar.protect(kMapOpts, AddTag(map_addr, kTestTag), kVmarSize), ZX_ERR_INVALID_ARGS);
+ ASSERT_OK(vmar.protect(kMapOpts, map_addr, kVmarSize));
+
+ auto IsUntagged = [](uintptr_t ptr) { return (ptr >> kTagShift) == 0; };
+ ASSERT_TRUE(IsUntagged(vmar_addr));
+ ASSERT_TRUE(IsUntagged(map_addr));
+
+ size_t actual = 0u;
+
+ // Write via the VMO...
+ ASSERT_OK(vmo.write(AddTag(buff, kTestTag), 0u, kVmoSize));
+
+ // ...then read via zx_process_read_memory. The kernel will treat a tagged vmar address normally,
+ // but fail when it sees there's no memory at the tagged address.
+ auto buf = std::make_unique<uint8_t[]>(kVmoSize);
+ ASSERT_STATUS(
+ zx::process::self()->read_memory(AddTag(vmar_addr, kTestTag), AddTag(buf.get(), kTestTag),
+ kVmoSize, AddTag(&actual, kTestTag)),
+ ZX_ERR_NO_MEMORY);
+ ASSERT_OK(zx::process::self()->read_memory(vmar_addr, AddTag(buf.get(), kTestTag), kVmoSize,
+ AddTag(&actual, kTestTag)));
+ ASSERT_EQ(actual, kVmoSize);
+ ASSERT_EQ(memcmp(buf.get(), buff, kVmoSize), 0);
+
+ // Shuffle the data that will be written.
+ std::reverse(buff, buff + kVmoSize);
+
+ // Now write via zx_process_write_memory...
+ ASSERT_STATUS(
+ zx::process::self()->write_memory(AddTag(vmar_addr, kTestTag), AddTag(buff, kTestTag),
+ kVmoSize, AddTag(&actual, kTestTag)),
+ ZX_ERR_NO_MEMORY);
+ ASSERT_OK(zx::process::self()->write_memory(vmar_addr, AddTag(buff, kTestTag), kVmoSize,
+ AddTag(&actual, kTestTag)));
+ ASSERT_EQ(actual, kVmoSize);
+
+ // ...then read via the VMO.
+ ASSERT_OK(vmo.read(AddTag(buf.get(), kTestTag), 0u, kVmoSize));
+ ASSERT_EQ(memcmp(buf.get(), buff, kVmoSize), 0);
+
+ // We're done with the vmo and vmar. Although they will be destroyed after
+ // exiting this scope, we can do some checks here on syscalls for unmapping and
+ // decommitting.
+ ASSERT_STATUS(
+ vmar.op_range(ZX_VMO_OP_DECOMMIT, AddTag(map_addr, kTestTag), kVmarSize, nullptr, 0u),
+ ZX_ERR_OUT_OF_RANGE);
+ ASSERT_OK(vmar.op_range(ZX_VMO_OP_DECOMMIT, map_addr, kVmarSize, nullptr, 0u));
+
+ ASSERT_STATUS(vmar.unmap(AddTag(vmar_addr, kTestTag), kVmarSize), ZX_ERR_INVALID_ARGS);
+ ASSERT_OK(vmar.unmap(vmar_addr, kVmarSize));
+}
+
#ifdef __clang__
[[clang::no_sanitize("all")]]
#endif
@@ -326,6 +404,71 @@
EXPECT_EQ(report.context.arch.u.arm_64.far, reinterpret_cast<uintptr_t>(&kUdf0));
}
+#ifdef __clang__
+[[clang::no_sanitize("all")]]
+#endif
+__NO_RETURN void
+DoNothing() {
+ zx_thread_exit();
+}
+
+TEST(TopByteIgnoreTests, ThreadStartTaggedAddress) {
+ std::unique_ptr<std::byte[]> thread_stack = std::make_unique<std::byte[]>(kThreadStackSize);
+ const uintptr_t pc = reinterpret_cast<uintptr_t>(DoNothing);
+ const uintptr_t sp = compute_initial_stack_pointer(
+ reinterpret_cast<uintptr_t>(thread_stack.get()), kThreadStackSize);
+
+ auto run_thread = [](uintptr_t pc, uintptr_t sp) {
+ constexpr std::string_view kThreadName = "TBI tagged entry/stack";
+ zx::thread thread;
+ ASSERT_OK(zx::thread::create(*zx::process::self(), kThreadName.data(), kThreadName.size(), 0,
+ &thread));
+
+ ASSERT_OK(thread.start(pc, sp, 0, 0));
+ zx_signals_t observed;
+ ASSERT_OK(
+ thread.wait_one(ZX_THREAD_TERMINATED, zx::time::infinite(), AddTag(&observed, kTestTag)));
+ ASSERT_TRUE(observed & ZX_THREAD_TERMINATED);
+ };
+
+ // Both the PC and SP can be tagged.
+ run_thread(AddTag(pc, kTestTag), sp);
+ run_thread(pc, AddTag(sp, kTestTag));
+}
+
+TEST(TopByteIgnoreTests, ProcessStartTaggedAddress) {
+ auto run_process = [](uint8_t pc_tag, uint8_t sp_tag) {
+ zx::process proc;
+ zx::thread thread;
+ zx::vmar vmar;
+
+ constexpr std::string_view kTestName = "TBI process";
+ ASSERT_OK(zx::process::create(*zx::job::default_job(), kTestName.data(), kTestName.size(), 0,
+ &proc, &vmar));
+ ASSERT_OK(zx::thread::create(proc, kTestName.data(), kTestName.size(), 0, &thread));
+
+ // The process will get no handles, but it can still make syscalls.
+ // The vDSO's e_entry points to zx_process_exit. So the process will
+ // enter at `zx_process_exit(ZX_HANDLE_INVALID);`.
+ uintptr_t entry;
+ EXPECT_OK(mini_process_load_vdso(proc.get(), vmar.get(), nullptr, &entry));
+
+ // The vDSO ABI needs a stack, though zx_process_exit actually might not.
+ uintptr_t stack_base, sp;
+ EXPECT_OK(mini_process_load_stack(vmar.get(), false, &stack_base, &sp));
+ zx_handle_close(vmar.get());
+
+ ASSERT_OK(proc.start(thread, AddTag(entry, pc_tag), AddTag(sp, sp_tag), zx::handle(), 0));
+
+ zx_signals_t signals;
+ EXPECT_OK(proc.wait_one(ZX_TASK_TERMINATED, zx::deadline_after(zx::sec(1)), &signals));
+ EXPECT_EQ(signals, ZX_TASK_TERMINATED);
+ };
+
+ run_process(kTestTag, 0);
+ run_process(0, kTestTag);
+}
+
#elif defined(__x86_64__)
TEST(TopByteIgnoreTests, AddressTaggingGetSystemFeaturesX86_64) {