1 : /*
2 : * Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 : * Use of this source code is governed by a BSD-style license that can be
4 : * found in the LICENSE file.
5 : */
6 :
7 : /*
8 : * nccopycode.c
9 : * Copies two code streams in a thread-safe way
10 : *
11 : */
12 :
13 : #include "native_client/src/include/portability.h"
14 :
15 : #if NACL_WINDOWS == 1
16 : #include <windows.h>
17 : #else
18 : #include <sys/mman.h>
19 : #endif
20 :
21 : #include <stdio.h>
22 : #include <stdlib.h>
23 : #include <errno.h>
24 : #include <string.h>
25 : #include <assert.h>
26 : #include "native_client/src/include/nacl_platform.h"
27 : #include "native_client/src/shared/platform/nacl_check.h"
28 : #include "native_client/src/trusted/service_runtime/sel_ldr.h"
29 : #include "native_client/src/trusted/service_runtime/sel_memory.h"
30 : #include "native_client/src/trusted/validator/ncvalidate.h"
31 :
32 : #if NACL_ARCH(NACL_TARGET_ARCH) == NACL_x86
33 : # if NACL_TARGET_SUBARCH == 32
34 : # include "native_client/src/trusted/validator/x86/ncval_seg_sfi/ncdecode.h"
35 : # include "native_client/src/trusted/validator/x86/ncval_seg_sfi/ncvalidate.h"
36 : # elif NACL_TARGET_SUBARCH == 64
37 : # include "native_client/src/trusted/validator/x86/decoder/nc_inst_iter.h"
38 : # include "native_client/src/trusted/validator/x86/decoder/nc_inst_state_internal.h"
39 : # include "native_client/src/trusted/validator/x86/nacl_cpuid.h"
40 : # include "native_client/src/trusted/validator/x86/ncval_reg_sfi/ncval_decode_tables.h"
41 : # include "native_client/src/trusted/validator/x86/nc_segment.h"
42 : # else
43 : # error "Unknown Platform"
44 : # endif
45 : #else
46 : # error "Unknown Platform"
47 : #endif
48 :
49 : /* x86 HALT opcode */
50 : static const uint8_t kNaClFullStop = 0xf4;
51 :
52 : /*
53 : * Max size of aligned writes we may issue to code without syncing.
54 : * 8 is a safe value according to:
55 : * [1] Advance Micro Devices Inc. AMD64 Architecture Program-
56 : * mers Manual Volume 1: Application Programming, 2009.
57 : * [2] Intel Inc. Intel 64 and IA-32 Architectures Software Developers
58 : * Manual Volume 3A: System Programming Guide, Part 1, 2010.
59 : * [3] Vijay Sundaresan, Daryl Maier, Pramod Ramarao, and Mark
60 : * Stoodley. Experiences with multi-threading and dynamic class
61 : * loading in a java just-in-time compiler. Code Generation and
62 : * Optimization, IEEE/ACM International Symposium on, 0:87–
63 : * 97, 2006.
64 : */
65 : static const int kTrustAligned = 8;
66 :
67 : /*
68 : * Max size of unaligned writes we may issue to code.
69 : * Empirically this may be larger, however no docs to support it.
70 : * 1 means disabled.
71 : */
72 : static const int kTrustUnaligned = 1;
73 :
74 : /*
75 : * Boundary no write may ever cross.
76 : * On AMD machines must be leq 8. Intel machines leq cache line.
77 : */
78 : static const int kInstructionFetchSize = 8;
79 :
80 : /* defined in nccopycode_stores.S */
81 : void _cdecl onestore_memmove4(uint8_t* dst, uint8_t* src);
82 :
83 : /* defined in nccopycode_stores.S */
84 : void _cdecl onestore_memmove8(uint8_t* dst, uint8_t* src);
85 :
86 0 : static INLINE int IsAligned(uint8_t *dst, int size, int align) {
87 0 : uintptr_t startaligned = ((uintptr_t)dst) & ~(align-1);
88 0 : uintptr_t stopaligned = ((uintptr_t)dst+size-1) & ~(align-1);
89 0 : return startaligned == stopaligned;
90 : }
91 :
92 : /*
93 : * Test if it is safe to issue a unsynced change to dst/size using a
94 : * writesize write. Outputs the offset to start the write at.
95 : * 1 if it is ok, 0 if it is not ok.
96 : */
97 : static int IsTrustedWrite(uint8_t *dst,
98 : int size,
99 : int writesize,
100 0 : intptr_t* offset) {
101 0 : if (size > writesize) {
102 0 : return 0;
103 : }
104 0 : if (!IsAligned(dst, size, kInstructionFetchSize)) {
105 0 : return 0;
106 : }
107 0 : if (writesize <= kTrustAligned && IsAligned(dst, size, writesize)) {
108 : /* aligned write is trusted */
109 0 : *offset = (intptr_t) dst & (writesize - 1);
110 0 : return 1;
111 : }
112 0 : if (writesize <= kTrustUnaligned) {
113 : /* unaligned write is trusted */
114 0 : *offset = 0;
115 0 : return 1;
116 : }
117 0 : return 0;
118 : }
119 :
120 : /* this is global to prevent a (very smart) compiler from optimizing it out */
121 : void* g_squashybuffer = NULL;
122 : char g_firstbyte = 0;
123 :
124 0 : static Bool SerializeAllProcessors() {
125 : /*
126 : * We rely on the OS mprotect() call to issue interprocessor interrupts,
127 : * which will cause other processors to execute an IRET, which is
128 : * serializing.
129 : *
130 : * This code is based on two main considerations:
131 : * 1. Only switching the page from exec to non-exec state is guaranteed
132 : * to invalidate processors' instruction caches.
133 : * 2. It's bad to have a page that is both writeable and executable,
134 : * even if that happens not simultaneously.
135 : */
136 :
137 0 : int size = NACL_MAP_PAGESIZE;
138 0 : if (NULL == g_squashybuffer) {
139 0 : if ((0 != NaCl_page_alloc(&g_squashybuffer, size)) ||
140 : (0 != NaCl_mprotect(g_squashybuffer, size, PROT_READ|PROT_WRITE))) {
141 0 : NaClLog(0,
142 : ("SerializeAllProcessors: initial squashybuffer allocation"
143 : " failed\n"));
144 0 : return FALSE;
145 : }
146 :
147 0 : NaClFillMemoryRegionWithHalt(g_squashybuffer, size);
148 0 : g_firstbyte = *(char *) g_squashybuffer;
149 0 : NaClLog(0, "SerializeAllProcessors: g_firstbyte is %d\n", g_firstbyte);
150 : }
151 :
152 0 : if ((0 != NaCl_mprotect(g_squashybuffer, size, PROT_READ|PROT_EXEC))) {
153 0 : NaClLog(0,
154 : ("SerializeAllProcessors: interprocessor interrupt"
155 : " generation failed: could not reverse shield polarity (1)\n"));
156 0 : return FALSE;
157 : }
158 : /*
159 : * Make a read to ensure that the potential kernel laziness
160 : * would not affect this hack.
161 : */
162 0 : if (*(char *) g_squashybuffer != g_firstbyte) {
163 0 : NaClLog(0,
164 : ("SerializeAllProcessors: interprocessor interrupt"
165 : " generation failed: could not reverse shield polarity (2)\n"));
166 0 : NaClLog(0, "SerializeAllProcessors: g_firstbyte is %d\n", g_firstbyte);
167 0 : NaClLog(0, "SerializeAllProcessors: *g_squashybuffer is %d\n",
168 : *(char *) g_squashybuffer);
169 0 : return FALSE;
170 : }
171 : /*
172 : * We would like to set the protection to PROT_NONE, but on Windows
173 : * there's an ugly hack in NaCl_mprotect where PROT_NONE can result
174 : * in MEM_DECOMMIT, causing the contents of the page(s) to be lost!
175 : */
176 0 : if (0 != NaCl_mprotect(g_squashybuffer, size, PROT_READ)) {
177 0 : NaClLog(0,
178 : ("SerializeAllProcessors: interprocessor interrupt"
179 : " generation failed: could not reverse shield polarity (3)\n"));
180 0 : return FALSE;
181 : }
182 0 : return TRUE;
183 : }
184 :
185 : /*
186 : * Copy a single instruction, avoiding the possibility of other threads
187 : * executing a partially changed instruction.
188 : */
189 : static Bool CopyInstructionInternal(uint8_t *dst,
190 : uint8_t *src,
191 0 : uint8_t sz) {
192 0 : intptr_t offset = 0;
193 0 : uint8_t *firstbyte_p = dst;
194 :
195 0 : while (sz > 0 && dst[0] == src[0]) {
196 : /* scroll to first changed byte */
197 0 : dst++, src++, sz--;
198 : }
199 :
200 0 : if (sz == 0) {
201 : /* instructions are identical, we are done */
202 0 : return TRUE;
203 : }
204 :
205 0 : while (sz > 0 && dst[sz-1] == src[sz-1]) {
206 : /* trim identical bytes at end */
207 0 : sz--;
208 : }
209 :
210 0 : if (sz == 1) {
211 : /* we assume a 1-byte change is atomic */
212 0 : *dst = *src;
213 0 : } else if (IsTrustedWrite(dst, sz, 4, &offset)) {
214 : uint8_t tmp[4];
215 0 : memcpy(tmp, dst-offset, sizeof tmp);
216 0 : memcpy(tmp+offset, src, sz);
217 0 : onestore_memmove4(dst-offset, tmp);
218 0 : } else if (IsTrustedWrite(dst, sz, 8, &offset)) {
219 : uint8_t tmp[8];
220 0 : memcpy(tmp, dst-offset, sizeof tmp);
221 0 : memcpy(tmp+offset, src, sz);
222 0 : onestore_memmove8(dst-offset, tmp);
223 : } else {
224 : /* the slow path, first flip first byte to halt*/
225 0 : uint8_t firstbyte = firstbyte_p[0];
226 0 : firstbyte_p[0] = kNaClFullStop;
227 :
228 0 : if (!SerializeAllProcessors()) return FALSE;
229 :
230 : /* copy the rest of instruction */
231 0 : if (dst == firstbyte_p) {
232 : /* but not the first byte! */
233 0 : firstbyte = *src;
234 0 : dst++, src++, sz--;
235 : }
236 0 : memcpy(dst, src, sz);
237 :
238 0 : if (!SerializeAllProcessors()) return FALSE;
239 :
240 : /* flip first byte back */
241 0 : firstbyte_p[0] = firstbyte;
242 : }
243 0 : return TRUE;
244 : }
245 :
246 : #if NACL_TARGET_SUBARCH == 32
247 :
248 : /*
249 : * Copy a single instruction, avoiding the possibility of other threads
250 : * executing a partially changed instruction.
251 : */
252 : static Bool CopyInstruction(NCDecoderStatePair* tthis,
253 : NCDecoderInst *dinst_old,
254 0 : NCDecoderInst *dinst_new) {
255 0 : NCRemainingMemory* mem_old = &dinst_old->dstate->memory;
256 0 : NCRemainingMemory* mem_new = &dinst_new->dstate->memory;
257 :
258 0 : return CopyInstructionInternal(mem_old->mpc,
259 : mem_new->mpc,
260 : mem_old->read_length);
261 : }
262 :
263 : int NCCopyCode(uint8_t *dst, uint8_t *src, NaClPcAddress vbase,
264 0 : size_t sz, int bundle_size) {
265 : NCDecoderState dst_dstate;
266 : NCDecoderInst dst_inst;
267 : NCDecoderState src_dstate;
268 : NCDecoderInst src_inst;
269 : NCDecoderStatePair pair;
270 0 : int result = 0;
271 :
272 0 : NCDecoderStateConstruct(&dst_dstate, dst, vbase, sz, &dst_inst, 1);
273 0 : NCDecoderStateConstruct(&src_dstate, src, vbase, sz, &src_inst, 1);
274 0 : NCDecoderStatePairConstruct(&pair, &dst_dstate, &src_dstate);
275 0 : pair.action_fn = CopyInstruction;
276 0 : if (NCDecoderStatePairDecode(&pair)) result = 1;
277 0 : NCDecoderStatePairDestruct(&pair);
278 0 : NCDecoderStateDestruct(&src_dstate);
279 0 : NCDecoderStateDestruct(&dst_dstate);
280 :
281 0 : return result;
282 : }
283 :
284 : NaClValidationStatus NACL_SUBARCH_NAME(ApplyValidatorCopy,
285 : NACL_TARGET_ARCH,
286 : NACL_TARGET_SUBARCH)
287 : (enum NaClSBKind sb_kind,
288 : uintptr_t guest_addr,
289 : uint8_t *data_old,
290 : uint8_t *data_new,
291 : size_t size,
292 : int bundle_size,
293 0 : NaClCPUFeaturesX86 *cpu_features) {
294 0 : NaClValidationStatus status = NaClValidationFailedNotImplemented;
295 0 : assert(NACL_SB_DEFAULT == sb_kind);
296 0 : if (bundle_size == 16 || bundle_size == 32) {
297 0 : if (!NaClArchSupported(cpu_features)) {
298 0 : status = NaClValidationFailedCpuNotSupported;
299 : } else {
300 0 : status = ((0 == NCCopyCode(data_old, data_new, guest_addr,
301 : size, bundle_size))
302 : ? NaClValidationFailed : NaClValidationSucceeded);
303 : }
304 : }
305 0 : return status;
306 : }
307 :
308 : #elif NACL_TARGET_SUBARCH == 64
309 :
310 : int NaClCopyCodeIter(uint8_t *dst, uint8_t *src,
311 : NaClPcAddress vbase, size_t size) {
312 : NaClSegment segment_old, segment_new;
313 : NaClInstIter *iter_old, *iter_new;
314 : NaClInstState *istate_old, *istate_new;
315 : int still_good = 1;
316 :
317 : NaClSegmentInitialize(dst, vbase, size, &segment_old);
318 : NaClSegmentInitialize(src, vbase, size, &segment_new);
319 :
320 : iter_old = NaClInstIterCreate(kNaClValDecoderTables, &segment_old);
321 : if (NULL == iter_old) return 0;
322 : iter_new = NaClInstIterCreate(kNaClValDecoderTables, &segment_new);
323 : if (NULL == iter_new) {
324 : NaClInstIterDestroy(iter_old);
325 : return 0;
326 : }
327 : while (1) {
328 : /* March over every instruction, which means NaCl pseudo-instructions are
329 : * treated as multiple instructions. Checks in NaClValidateCodeReplacement
330 : * guarantee that only valid replacements will happen, and no pseudo-
331 : * instructions should be touched.
332 : */
333 : if (!(NaClInstIterHasNext(iter_old) && NaClInstIterHasNext(iter_new))) {
334 : if (NaClInstIterHasNext(iter_old) || NaClInstIterHasNext(iter_new)) {
335 : NaClLog(LOG_ERROR,
336 : "Segment replacement: copy failed: iterators "
337 : "length mismatch\n");
338 : still_good = 0;
339 : }
340 : break;
341 : }
342 : istate_old = NaClInstIterGetState(iter_old);
343 : istate_new = NaClInstIterGetState(iter_new);
344 : if (istate_old->bytes.length != istate_new->bytes.length ||
345 : iter_old->memory.read_length != iter_new->memory.read_length ||
346 : istate_new->inst_addr != istate_old->inst_addr) {
347 : /* Sanity check: this should never happen based on checks in
348 : * NaClValidateInstReplacement.
349 : */
350 : NaClLog(LOG_ERROR,
351 : "Segment replacement: copied instructions misaligned\n");
352 : still_good = 0;
353 : break;
354 : }
355 : /* Replacing all modified instructions at once could yield a speedup here
356 : * as every time we modify instructions we must serialize all processors
357 : * twice. Re-evaluate if code modification performance is an issue.
358 : */
359 : if (!CopyInstructionInternal(iter_old->memory.mpc,
360 : iter_new->memory.mpc,
361 : iter_old->memory.read_length)) {
362 : NaClLog(LOG_ERROR,
363 : "Segment replacement: copy failed: unable to copy instruction\n");
364 : still_good = 0;
365 : break;
366 : }
367 : NaClInstIterAdvance(iter_old);
368 : NaClInstIterAdvance(iter_new);
369 : }
370 :
371 : NaClInstIterDestroy(iter_old);
372 : NaClInstIterDestroy(iter_new);
373 : return still_good;
374 : }
375 :
376 : NaClValidationStatus NACL_SUBARCH_NAME(ApplyValidatorCopy,
377 : NACL_TARGET_ARCH,
378 : NACL_TARGET_SUBARCH)
379 : (enum NaClSBKind sb_kind,
380 : uintptr_t guest_addr,
381 : uint8_t *data_old,
382 : uint8_t *data_new,
383 : size_t size,
384 : int bundle_size,
385 : NaClCPUFeaturesX86 *cpu_features) {
386 : NaClValidationStatus status = NaClValidationFailedNotImplemented;
387 : assert(NACL_SB_DEFAULT == sb_kind);
388 : if (bundle_size == 16 || bundle_size == 32) {
389 : if (!NaClArchSupported(cpu_features)) {
390 : status = NaClValidationFailedCpuNotSupported;
391 : } else {
392 : status = ((0 == NaClCopyCodeIter(data_old, data_new, guest_addr, size))
393 : ? NaClValidationFailed : NaClValidationSucceeded);
394 : }
395 : }
396 : return status;
397 : }
398 :
399 : #endif
|