1+ #include < echion/danger.h>
2+ #include < echion/state.h>
3+
4+ #include < signal.h>
5+ #include < string.h>
6+ #include < sys/mman.h>
7+ #include < unistd.h>
8+ #include < algorithm>
9+ #include < cassert>
10+ #include < cerrno>
11+ #include < csetjmp>
12+ #include < cstdio>
13+
14+ static const size_t page_size = []() -> size_t {
15+ auto v = sysconf (_SC_PAGESIZE);
16+
17+ #ifdef PL_DARWIN
18+ if (v <= 0 )
19+ {
20+ // Fallback on macOS just in case
21+ v = getpagesize ();
22+ }
23+ #endif
24+
25+ if (v <= 0 )
26+ {
27+ fprintf (stderr, " Failed to detect page size, falling back to 4096\n " );
28+ return 4096 ;
29+ }
30+
31+ return v;
32+ }();
33+
34+ struct sigaction g_old_segv;
35+ struct sigaction g_old_bus;
36+
37+ thread_local ThreadAltStack t_altstack;
38+
39+ // We "arm" by publishing a valid jmp env for this thread.
40+ thread_local sigjmp_buf t_jmpenv;
41+ thread_local volatile sig_atomic_t t_handler_armed = 0 ;
42+
43+ static inline void arm_fault_handler ()
44+ {
45+ t_handler_armed = 1 ;
46+ __asm__ __volatile__ (" " ::: " memory" );
47+ }
48+
49+ static inline void disarm_fault_handler ()
50+ {
51+ __asm__ __volatile__ (" " ::: " memory" );
52+ t_handler_armed = 0 ;
53+ }
54+
55+ static void segv_handler (int signo, siginfo_t *, void *)
56+ {
57+ if (!t_handler_armed)
58+ {
59+ struct sigaction * old = (signo == SIGSEGV) ? &g_old_segv : &g_old_bus;
60+ // Restore the previous handler and re-raise so default/old handling occurs.
61+ sigaction (signo, old, nullptr );
62+ raise (signo);
63+ return ;
64+ }
65+
66+ // Jump back to the armed site. Use 1 so sigsetjmp returns nonzero.
67+ siglongjmp (t_jmpenv, 1 );
68+ }
69+
70+ int init_segv_catcher ()
71+ {
72+ if (t_altstack.ensure_installed () != 0 )
73+ {
74+ return -1 ;
75+ }
76+
77+ struct sigaction sa
78+ {
79+ };
80+ sa.sa_sigaction = segv_handler;
81+ sigemptyset (&sa.sa_mask );
82+
83+ // SA_SIGINFO for 3-arg handler; SA_ONSTACK to run on alt stack; SA_NODEFER to avoid having to
84+ // use savemask
85+ sa.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_NODEFER;
86+ if (sigaction (SIGSEGV, &sa, &g_old_segv) != 0 )
87+ {
88+ return -1 ;
89+ }
90+ if (sigaction (SIGBUS, &sa, &g_old_bus) != 0 )
91+ {
92+ // Try to roll back SIGSEGV install on failure.
93+ sigaction (SIGSEGV, &g_old_segv, nullptr );
94+ return -1 ;
95+ }
96+
97+ return 0 ;
98+ }
99+
100+ #if defined PL_LINUX
101+ using safe_memcpy_return_t = ssize_t ;
102+ #elif defined PL_DARWIN
103+ using safe_memcpy_return_t = mach_vm_size_t ;
104+ #endif
105+
106+ safe_memcpy_return_t safe_memcpy (void * dst, const void * src, size_t n)
107+ {
108+ if (t_altstack.ensure_installed () != 0 )
109+ {
110+ errno = EINVAL;
111+ return -1 ;
112+ }
113+
114+ bool t_faulted = false ;
115+
116+ auto * d = static_cast <uint8_t *>(dst);
117+ auto * s = static_cast <const uint8_t *>(src);
118+ safe_memcpy_return_t rem = static_cast <safe_memcpy_return_t >(n);
119+
120+ arm_fault_handler ();
121+ if (sigsetjmp (t_jmpenv, /* save sig mask = */ 0 ) != 0 )
122+ {
123+ // We arrived here from siglongjmp after a fault.
124+ t_faulted = true ;
125+ goto landing;
126+ }
127+
128+ // Copy in page-bounded chunks (at most one fault per bad page).
129+ while (rem)
130+ {
131+ safe_memcpy_return_t to_src_pg =
132+ page_size - (static_cast <uintptr_t >(reinterpret_cast <uintptr_t >(s)) & (page_size - 1 ));
133+ safe_memcpy_return_t to_dst_pg =
134+ page_size - (static_cast <uintptr_t >(reinterpret_cast <uintptr_t >(d)) & (page_size - 1 ));
135+ safe_memcpy_return_t chunk = std::min (rem, std::min (to_src_pg, to_dst_pg));
136+
137+ // Optional early probe to fault before entering large memcpy
138+ (void )*reinterpret_cast <volatile const uint8_t *>(s);
139+
140+ // If this faults, we'll siglongjmp back to the sigsetjmp above.
141+ (void )memcpy (d, s, static_cast <size_t >(chunk));
142+
143+ d += chunk;
144+ s += chunk;
145+ rem -= chunk;
146+ }
147+
148+ landing:
149+ disarm_fault_handler ();
150+
151+ if (t_faulted)
152+ {
153+ errno = EFAULT;
154+ return -1 ;
155+ }
156+
157+ return static_cast <safe_memcpy_return_t >(n);
158+ }
159+
160+ #if defined PL_LINUX
161+ ssize_t safe_memcpy_wrapper (pid_t , const struct iovec * __dstvec, unsigned long int __dstiovcnt,
162+ const struct iovec * __srcvec, unsigned long int __srciovcnt,
163+ unsigned long int )
164+ {
165+ (void )__dstiovcnt;
166+ (void )__srciovcnt;
167+ assert (__dstiovcnt == 1 );
168+ assert (__srciovcnt == 1 );
169+
170+ size_t to_copy = std::min (__dstvec->iov_len , __srcvec->iov_len );
171+ return safe_memcpy (__dstvec->iov_base , __srcvec->iov_base , to_copy);
172+ }
173+ #elif defined PL_DARWIN
174+ kern_return_t safe_memcpy_wrapper (vm_map_read_t target_task, mach_vm_address_t address,
175+ mach_vm_size_t size, mach_vm_address_t data,
176+ mach_vm_size_t * outsize)
177+ {
178+ (void )target_task;
179+
180+ auto copied = safe_memcpy (reinterpret_cast <void *>(data), reinterpret_cast <void *>(address),
181+ static_cast <size_t >(size));
182+ *outsize = copied;
183+ return copied == size ? KERN_SUCCESS : KERN_FAILURE;
184+ }
185+ #endif
0 commit comments