Branch data Line data Source code
1 : : // Copyright 2026 HPActor Contributors
2 : : //
3 : : // Licensed under the Apache License, Version 2.0 (the "License");
4 : : // you may not use this file except in compliance with the License.
5 : : // You may obtain a copy of the License at
6 : : //
7 : : // http://www.apache.org/licenses/LICENSE-2.0
8 : : //
9 : : // Unless required by applicable law or agreed to in writing, software
10 : : // distributed under the License is distributed on an "AS IS" BASIS,
11 : : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : : // See the License for the specific language governing permissions and
13 : : // limitations under the License.
14 : :
15 : : #include <hpactor/mem/guard_page.hpp>
16 : : #include <hpactor/platform.hpp>
17 : :
18 : : #include <cstdio>
19 : : #include <cstdlib>
20 : : #include <cstring>
21 : : #include <sys/mman.h>
22 : : #include <unistd.h>
23 : :
24 : : namespace hpactor::mem {
25 : :
26 : 64 : size_t page_size() noexcept {
27 : 64 : static size_t ps = static_cast<size_t>(sysconf(_SC_PAGESIZE));
28 : 64 : return ps;
29 : : }
30 : :
31 : 30 : void* guarded_alloc(size_t user_bytes) noexcept {
32 : 30 : size_t ps = page_size();
33 : : // Ensure at least one byte of usable space, even for user_bytes == 0,
34 : : // so that the returned pointer is always inside the writable region
35 : : // (not on the trailing guard page).
36 : : // Layout: [guard page] [usable (page-aligned)] [guard page]
37 : 30 : size_t clamped = user_bytes > 0 ? user_bytes : 1;
38 : 30 : size_t user_pages = ((clamped + ps - 1) / ps) * ps;
39 : 30 : size_t total = ps + user_pages + ps;
40 : :
41 : 30 : void* base = mmap(nullptr, total, PROT_READ | PROT_WRITE,
42 : : MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
43 : 30 : if (base == MAP_FAILED)
44 : 0 : return nullptr;
45 : :
46 : : // Protect leading guard page
47 : 30 : if (mprotect(base, ps, PROT_NONE) < 0) {
48 : 0 : munmap(base, total);
49 : 0 : return nullptr;
50 : : }
51 : :
52 : : // Protect trailing guard page (page-aligned)
53 : 30 : auto* trailing = static_cast<std::byte*>(base) + ps + user_pages;
54 : 30 : if (mprotect(trailing, ps, PROT_NONE) < 0) {
55 : 0 : mprotect(base, ps, PROT_READ | PROT_WRITE);
56 : 0 : munmap(base, total);
57 : 0 : return nullptr;
58 : : }
59 : :
60 : 30 : return static_cast<std::byte*>(base) + ps;
61 : : }
62 : :
63 : 31 : void guarded_free(void* user_ptr, size_t user_bytes) noexcept {
64 : 31 : if (!user_ptr)
65 : 1 : return;
66 : 30 : size_t ps = page_size();
67 : : // Must match the same clamping as guarded_alloc for consistency
68 : 30 : size_t clamped = user_bytes > 0 ? user_bytes : 1;
69 : 30 : size_t user_pages = ((clamped + ps - 1) / ps) * ps;
70 : 30 : void* base = static_cast<std::byte*>(user_ptr) - ps;
71 : 30 : size_t total = ps + user_pages + ps;
72 : 30 : munmap(base, total);
73 : : }
74 : :
75 : : // ---------------------------------------------------------------------------
76 : : // Corruption signal handler
77 : : // ---------------------------------------------------------------------------
78 : :
79 : : namespace {
80 : : // Previous signal handler (chained on non-corruption faults)
81 : : struct sigaction g_prev_action;
82 : : bool g_handler_installed = false;
83 : :
84 : : // Pre-opened fd for signal-safe logging. Use write() instead of the logger
85 : : // in signal context — logger CAS atomics may deadlock.
86 : : int g_guard_page_fd = -1;
87 : :
88 : 0 : void corruption_sigaction(int sig, siginfo_t* info, void* ctx) {
89 : 0 : if (!info || !info->si_addr) {
90 : : // Chain to previous handler
91 : 0 : if (g_prev_action.sa_sigaction) {
92 : 0 : g_prev_action.sa_sigaction(sig, info, ctx);
93 : : }
94 : 0 : return;
95 : : }
96 : :
97 : 0 : void* fault_addr = info->si_addr;
98 : :
99 : : // Try to identify the owning segment
100 : 0 : auto seg_info = SegmentProvider::instance().lookup(fault_addr);
101 : 0 : if (seg_info.base != nullptr) {
102 : : // This is our memory — corruption detected
103 : : // Use direct write() to pre-opened fd — never use the logger in
104 : : // signal context (CAS atomics may deadlock).
105 : 0 : if (g_guard_page_fd >= 0) {
106 : 0 : const char* msg = "HPActor: guard page violation at %p (segment "
107 : : "base %p, size %zu) — memory corruption\n";
108 : : char buf[256];
109 : 0 : int len = snprintf(buf, sizeof(buf), msg, fault_addr, seg_info.base,
110 : : seg_info.size);
111 : 0 : write(g_guard_page_fd, buf, static_cast<size_t>(len));
112 : : }
113 : 0 : _exit(EXIT_FAILURE);
114 : : }
115 : :
116 : : // Not our memory — chain to previous handler
117 : 0 : if (g_prev_action.sa_sigaction) {
118 : 0 : g_prev_action.sa_sigaction(sig, info, ctx);
119 : : } else {
120 : : // No previous handler, restore default and re-raise
121 : 0 : signal(sig, SIG_DFL);
122 : 0 : raise(sig);
123 : : }
124 : : }
125 : : } // namespace
126 : :
127 : 2 : void set_guard_page_log_fd(int fd) noexcept {
128 : 2 : g_guard_page_fd = fd;
129 : 2 : }
130 : :
131 : 3 : void install_corruption_handler() noexcept {
132 : 3 : if (g_handler_installed)
133 : 1 : return;
134 : :
135 : : struct sigaction sa;
136 : 2 : std::memset(&sa, 0, sizeof(sa));
137 : 2 : sa.sa_sigaction = corruption_sigaction;
138 : 2 : sa.sa_flags = SA_SIGINFO | SA_NODEFER;
139 : :
140 : 2 : sigaction(SIGSEGV, &sa, &g_prev_action);
141 : 2 : g_handler_installed = true;
142 : : }
143 : :
144 : 2 : void remove_corruption_handler() noexcept {
145 : 2 : if (!g_handler_installed)
146 : 1 : return;
147 : :
148 : 1 : sigaction(SIGSEGV, &g_prev_action, nullptr);
149 : 1 : g_handler_installed = false;
150 : : }
151 : :
152 : : } // namespace hpactor::mem
|