2222extern " C" int safefetch32_cont (int * adr, int errValue);
2323extern " C" int64_t safefetch64_cont (int64_t * adr, int64_t errValue);
2424
25+ // safecopy_impl copies `len` bytes from `src` to `dst` one byte at a time and
26+ // returns 1 on success. If any load from `src` faults, the signal handler
27+ // redirects the pc anywhere in [safecopy_impl, safecopy_cont) to safecopy_cont,
28+ // which returns 0. Both are leaf routines with no stack frame, so the redirect
29+ // keeps the return address intact.
30+ extern " C" int safecopy_impl (void * dst, const void * src, size_t len);
31+ extern " C" int safecopy_cont (void * dst, const void * src, size_t len);
32+
2533#ifdef __APPLE__
2634 #if defined(__x86_64__)
2735 #define current_pc context_rip
@@ -67,6 +75,25 @@ extern "C" int64_t safefetch64_cont(int64_t* adr, int64_t errValue);
6775 _safefetch64_cont:
6876 movq %rsi, %rax
6977 ret
78+ .globl _safecopy_impl
79+ .private_extern _safecopy_impl
80+ _safecopy_impl:
81+ xorl %ecx, %ecx
82+ Lsafecopy_loop:
83+ cmpq %rdx, %rcx
84+ jae Lsafecopy_done
85+ movzbl (%rsi,%rcx,1), %eax
86+ movb %al, (%rdi,%rcx,1)
87+ incq %rcx
88+ jmp Lsafecopy_loop
89+ Lsafecopy_done:
90+ movl $1, %eax
91+ ret
92+ .globl _safecopy_cont
93+ .private_extern _safecopy_cont
94+ _safecopy_cont:
95+ xorl %eax, %eax
96+ ret
7097 )" );
7198 #else
7299 asm (R"(
@@ -95,6 +122,27 @@ extern "C" int64_t safefetch64_cont(int64_t* adr, int64_t errValue);
95122 safefetch64_cont:
96123 movq %rsi, %rax
97124 ret
125+ .globl safecopy_impl
126+ .hidden safecopy_impl
127+ .type safecopy_impl, %function
128+ safecopy_impl:
129+ xorl %ecx, %ecx
130+ .Lsafecopy_loop:
131+ cmpq %rdx, %rcx
132+ jae .Lsafecopy_done
133+ movzbl (%rsi,%rcx,1), %eax
134+ movb %al, (%rdi,%rcx,1)
135+ incq %rcx
136+ jmp .Lsafecopy_loop
137+ .Lsafecopy_done:
138+ movl $1, %eax
139+ ret
140+ .globl safecopy_cont
141+ .hidden safecopy_cont
142+ .type safecopy_cont, %function
143+ safecopy_cont:
144+ xorl %eax, %eax
145+ ret
98146 )" );
99147 #endif // __APPLE__
100148#elif defined(__aarch64__)
@@ -120,6 +168,25 @@ extern "C" int64_t safefetch64_cont(int64_t* adr, int64_t errValue);
120168 _safefetch64_cont:
121169 mov x0, x1
122170 ret
171+ .globl _safecopy_impl
172+ .private_extern _safecopy_impl
173+ _safecopy_impl:
174+ mov x3, xzr
175+ Lsafecopy_loop:
176+ cmp x3, x2
177+ b.hs Lsafecopy_done
178+ ldrb w4, [x1, x3]
179+ strb w4, [x0, x3]
180+ add x3, x3, #1
181+ b Lsafecopy_loop
182+ Lsafecopy_done:
183+ mov w0, #1
184+ ret
185+ .globl _safecopy_cont
186+ .private_extern _safecopy_cont
187+ _safecopy_cont:
188+ mov w0, #0
189+ ret
123190 )" );
124191 #else
125192 asm (R"(
@@ -148,61 +215,39 @@ extern "C" int64_t safefetch64_cont(int64_t* adr, int64_t errValue);
148215 safefetch64_cont:
149216 mov x0, x1
150217 ret
218+ .globl safecopy_impl
219+ .hidden safecopy_impl
220+ .type safecopy_impl, %function
221+ safecopy_impl:
222+ mov x3, xzr
223+ .Lsafecopy_loop:
224+ cmp x3, x2
225+ b.hs .Lsafecopy_done
226+ ldrb w4, [x1, x3]
227+ strb w4, [x0, x3]
228+ add x3, x3, #1
229+ b .Lsafecopy_loop
230+ .Lsafecopy_done:
231+ mov w0, #1
232+ ret
233+ .globl safecopy_cont
234+ .hidden safecopy_cont
235+ .type safecopy_cont, %function
236+ safecopy_cont:
237+ mov w0, #0
238+ ret
151239 )" );
152240 #endif
153241#endif
154242
155243bool SafeAccess::safeCopy (void * dst, const void * src, size_t len) {
156- // Two-sentinel pattern (same as isReadable): a real-data word may equal
157- // one sentinel by chance, but not both — if both fetches return their
158- // sentinel, the access truly faulted.
159- //
160- // All safefetch32 loads issued here use 4-byte-aligned addresses. Pages
161- // are 4 KiB (or 16 KiB on Apple Silicon), both divisible by 4, so an
162- // aligned 4-byte load never spans a page boundary. The only fault
163- // possible is when the aligned address itself lies in an unmapped page;
164- // we never spuriously fault on an over-read past `src + len`.
165- static const int32_t SENT_A = (int32_t )0x55AA55AA ;
166- static const int32_t SENT_B = (int32_t )0xAA55AA55 ;
167- uint8_t * d = (uint8_t *)dst;
168- const uint8_t * s = (const uint8_t *)src;
169- size_t i = 0 ;
170-
171- // Front fixup: if `src` is not 4-byte aligned, fetch at the previous
172- // aligned address (1..3 bytes before src). That address lies in the
173- // same 4-byte word as src — and since pages are 4-byte aligned, in
174- // the same page as src. The leading k bytes of the fetched word lie
175- // before the caller's range and are discarded via the +k offset; they
176- // never reach `dst`.
177- size_t k = (uintptr_t )s & 3u ;
178- if (k != 0 && i < len) {
179- int32_t * aligned = (int32_t *)(s - k);
180- int32_t v1 = safefetch32_impl (aligned, SENT_A );
181- int32_t v2 = safefetch32_impl (aligned, SENT_B );
182- if (v1 == SENT_A && v2 == SENT_B ) {
183- return false ;
184- }
185- size_t take = (4 - k < len) ? (4 - k) : len;
186- memcpy (d, ((const uint8_t *)&v1) + k, take);
187- i = take;
188- }
189-
190- // Middle + tail: (s + i) is now 4-byte aligned. The final iteration may
191- // load up to 3 over-read bytes past `src + len`, but those bytes sit in
192- // the same 4-byte-aligned word and therefore the same page as the bytes
193- // we actually wanted — never a fault from the over-read alone.
194- while (i < len) {
195- int32_t * aligned = (int32_t *)(s + i);
196- int32_t v1 = safefetch32_impl (aligned, SENT_A );
197- int32_t v2 = safefetch32_impl (aligned, SENT_B );
198- if (v1 == SENT_A && v2 == SENT_B ) {
199- return false ;
200- }
201- size_t chunk = (len - i >= 4 ) ? 4 : (len - i);
202- memcpy (d + i, &v1, chunk); // memcpy from local — no UAF risk
203- i += chunk;
204- }
205- return true ;
244+ // The copy runs entirely inside the safecopy_impl assembly stub, which
245+ // reads `src` one byte at a time. If a load faults, handle_safefetch
246+ // redirects execution to safecopy_cont, which returns 0. Because the copy
247+ // is byte-granular it only ever touches bytes in [src, src+len) — there is
248+ // no over-read past the requested range and no alignment reasoning needed.
249+ // A fault mid-copy may leave up to len-1 bytes already written to `dst`.
250+ return safecopy_impl (dst, src, len) != 0 ;
206251}
207252
208253bool SafeAccess::handle_safefetch (int sig, void * context) {
@@ -215,6 +260,11 @@ bool SafeAccess::handle_safefetch(int sig, void* context) {
215260 } else if (pc == (uintptr_t )safefetch64_impl) {
216261 uc->current_pc = (uintptr_t )safefetch64_cont;
217262 return true ;
263+ } else if (pc >= (uintptr_t )safecopy_impl && pc < (uintptr_t )safecopy_cont) {
264+ // Unlike safefetch, the faulting load can be at any pc inside the copy
265+ // loop, so match the whole [safecopy_impl, safecopy_cont) range.
266+ uc->current_pc = (uintptr_t )safecopy_cont;
267+ return true ;
218268 }
219269 }
220270 return false ;
0 commit comments