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 : #include <errno.h>
8 : #include <string.h>
9 :
10 : #include "native_client/src/include/portability.h"
11 :
12 : #include "native_client/src/shared/platform/nacl_check.h"
13 : #include "native_client/src/shared/platform/nacl_host_desc.h"
14 : #include "native_client/src/shared/platform/nacl_log.h"
15 :
16 : #include "native_client/src/trusted/gio/gio_shm.h"
17 : #include "native_client/src/trusted/service_runtime/include/bits/mman.h"
18 : #include "native_client/src/trusted/service_runtime/include/sys/stat.h"
19 : #include "native_client/src/trusted/service_runtime/nacl_config.h"
20 : #include "native_client/src/trusted/service_runtime/sel_util.h"
21 :
22 : #include "native_client/src/trusted/desc/nacl_desc_base.h"
23 : #include "native_client/src/trusted/desc/nacl_desc_effector_trusted_mem.h"
24 : #include "native_client/src/trusted/desc/nacl_desc_imc_shm.h"
25 :
26 : /*
27 : * This code maps in GIO_SHM_WINDOWSIZE bytes at a time for doing
28 : * "I/O" from/to the shared memory object. This value must be an
29 : * integer multiple of NACL_MAP_PAGESIZE.
30 : */
31 : #define GIO_SHM_WINDOWSIZE (16 * NACL_MAP_PAGESIZE)
32 :
33 : /*
34 : * Release current window if it exists, then map in window at the
35 : * provided new_window_offset. This is akin to filbuf.
36 : *
37 : * Preconditions: 0 == (new_win_offset & (NACL_MAP_PAGESIZE - 1))
38 : * new_win_offset < self->shm_sz
39 : */
40 6 : static int NaClGioShmSetWindow(struct NaClGioShm *self,
41 6 : size_t new_win_offset) {
42 6 : uintptr_t map_result;
43 6 : size_t actual_len;
44 :
45 6 : NaClLog(4,
46 : "NaClGioShmSetWindow: new_win_offset 0x%"NACL_PRIxS"\n",
47 : new_win_offset);
48 6 : if (0 != (new_win_offset & (NACL_MAP_PAGESIZE - 1))) {
49 0 : NaClLog(LOG_FATAL,
50 : ("NaClGioShmSetWindow: internal error, requested"
51 : " new window offset 0x%"NACL_PRIxS" is not aligned.\n"),
52 : new_win_offset);
53 0 : }
54 :
55 6 : if (new_win_offset >= self->shm_sz) {
56 0 : NaClLog(LOG_FATAL,
57 : ("NaClGioShmSetWindow: setting window beyond end of shm object"
58 : " offset 0x%"NACL_PRIxS", size 0x%"NACL_PRIxS"\n"),
59 : new_win_offset, self->shm_sz);
60 0 : }
61 :
62 6 : if (NULL != self->cur_window) {
63 0 : NaClDescUnmapUnsafe(self->shmp, (void *) self->cur_window,
64 : self->window_size);
65 0 : }
66 6 : self->cur_window = NULL;
67 6 : self->window_size = 0;
68 :
69 : /*
70 : * The Map virtual function will NOT pad space beyond the end of the
71 : * memory mapping object with zero-filled pages. This is done for
72 : * user code in nacl_syscall_common.c (NaClSysMmap), and the Map
73 : * virtual function exposes the behavioral inconsistencies wrt
74 : * allowing but ignoring mapping an offset beyond the end of file
75 : * (linux) versus disallowing the mapping (MapViewOfFileEx).
76 : *
77 : * Here, we know the actual size of the shm object, and can deal
78 : * with it.
79 : */
80 6 : actual_len = GIO_SHM_WINDOWSIZE;
81 6 : if (actual_len > self->shm_sz - new_win_offset) {
82 5 : actual_len = self->shm_sz - new_win_offset;
83 5 : }
84 : map_result =
85 12 : (*((struct NaClDescVtbl const *) self->shmp->base.vtbl)->
86 : Map)(self->shmp,
87 6 : NaClDescEffectorTrustedMem(),
88 : (void *) NULL,
89 : actual_len,
90 : NACL_ABI_PROT_READ | NACL_ABI_PROT_WRITE,
91 : NACL_ABI_MAP_SHARED,
92 : (nacl_off64_t) new_win_offset);
93 6 : NaClLog(4,
94 : "NaClGioShmSetWindow: Map returned 0x%"NACL_PRIxPTR"\n",
95 : map_result);
96 6 : if (NaClPtrIsNegErrno(&map_result)) {
97 0 : return 0;
98 : }
99 :
100 6 : self->cur_window = (char *) map_result;
101 6 : self->window_size = actual_len;
102 6 : self->window_offset = new_win_offset;
103 :
104 6 : return 1;
105 6 : }
106 :
107 997892 : static ssize_t NaClGioShmReadOrWrite(struct Gio *vself,
108 997892 : void *buf,
109 997892 : size_t count,
110 997892 : int is_write) {
111 997892 : struct NaClGioShm *self = (struct NaClGioShm *) vself;
112 997892 : size_t new_window_offset;
113 997892 : size_t transfer;
114 997892 : size_t window_end;
115 997892 : size_t window_remain;
116 997892 : size_t sofar;
117 :
118 997892 : NaClLog(4,
119 : ("NaClGioShmReadOrWrite: 0x%"NACL_PRIxPTR","
120 : " 0x%"NACL_PRIxPTR", 0x%"NACL_PRIxS", %d\n"),
121 : (uintptr_t) vself,
122 : (uintptr_t) buf,
123 : count,
124 : is_write);
125 997892 : sofar = 0;
126 2993675 : while (count > 0) {
127 997891 : NaClLog(4, "NaClGioShmReadOrWrite: count 0x%"NACL_PRIxS"\n", count);
128 997891 : if (self->io_offset >= self->shm_sz) {
129 0 : break;
130 : }
131 997891 : NaClLog(4, " cur_window 0x%"NACL_PRIxPTR"\n",
132 : (uintptr_t) self->cur_window);
133 997891 : NaClLog(4, " io_offset 0x%"NACL_PRIxS"\n", self->io_offset);
134 997891 : NaClLog(4, "window_offset 0x%"NACL_PRIxS"\n", self->window_offset);
135 2993673 : if (NULL == self->cur_window
136 : || self->io_offset < self->window_offset
137 : || self->window_offset + self->window_size <= self->io_offset) {
138 : /*
139 : * io_offset is outside the window. move the window so that
140 : * it's within.
141 : */
142 0 : NaClLog(4, "Seek required\n");
143 :
144 0 : new_window_offset = (self->io_offset
145 : & (~(((size_t) NACL_MAP_PAGESIZE) - 1)));
146 0 : NaClLog(4, "new_window_offset 0x%"NACL_PRIxS"\n", new_window_offset);
147 0 : CHECK(0 == (new_window_offset &
148 : (((size_t) NACL_MAP_PAGESIZE)-1)));
149 0 : if (!NaClGioShmSetWindow(self, new_window_offset)) {
150 0 : if (0 == sofar) {
151 0 : errno = EIO;
152 0 : sofar = -1;
153 0 : }
154 0 : return sofar;
155 : }
156 0 : } else {
157 997891 : NaClLog(4, "no seek required\n");
158 : }
159 997891 : NaClLog(4, " cur_window 0x%"NACL_PRIxPTR"\n",
160 : (uintptr_t) self->cur_window);
161 997891 : NaClLog(4, " io_offset 0x%"NACL_PRIxS"\n", self->io_offset);
162 997891 : NaClLog(4, "window_offset 0x%"NACL_PRIxS"\n", self->window_offset);
163 :
164 2993673 : CHECK(self->window_offset <= self->io_offset);
165 2993673 : CHECK(self->io_offset < self->window_offset + self->window_size);
166 :
167 997891 : transfer = count;
168 997891 : window_end = self->window_offset + self->window_size;
169 997891 : if (window_end > self->shm_sz) {
170 0 : window_end = self->shm_sz;
171 0 : }
172 997891 : window_remain = window_end - self->io_offset;
173 :
174 997891 : NaClLog(4, "remaining in window 0x%"NACL_PRIxS"\n", window_remain);
175 :
176 2993673 : CHECK(window_remain <= GIO_SHM_WINDOWSIZE);
177 :
178 997891 : if (transfer > window_remain) {
179 0 : transfer = window_remain;
180 0 : }
181 :
182 997891 : NaClLog(4, "transfer 0x%"NACL_PRIxS"\n", transfer);
183 :
184 997891 : if (is_write) {
185 400642 : NaClLog(4,
186 : ("about to \"write\" memcpy(0x%"NACL_PRIxPTR", "
187 : " 0x%"NACL_PRIxPTR", 0x%"NACL_PRIxS" bytes)\n"),
188 : (uintptr_t) (self->cur_window
189 : + (self->io_offset - self->window_offset)),
190 : (uintptr_t) buf,
191 : transfer);
192 :
193 1201926 : memcpy(self->cur_window + (self->io_offset - self->window_offset),
194 : buf,
195 : transfer);
196 400642 : } else {
197 597249 : NaClLog(4,
198 : ("about to \"read\" memcpy(0x%"NACL_PRIxPTR", "
199 : " 0x%"NACL_PRIxPTR", 0x%"NACL_PRIxS" bytes)\n"),
200 : (uintptr_t) buf,
201 : (uintptr_t) (self->cur_window
202 : + (self->io_offset - self->window_offset)),
203 : transfer);
204 :
205 1791747 : memcpy(buf,
206 : self->cur_window + (self->io_offset - self->window_offset),
207 : transfer);
208 : }
209 997891 : self->io_offset += transfer;
210 997891 : sofar += transfer;
211 :
212 997891 : buf = (void *)((uintptr_t) buf + transfer);
213 997891 : count -= transfer;
214 997891 : }
215 :
216 997892 : return sofar;
217 997892 : }
218 :
219 597250 : static ssize_t NaClGioShmRead(struct Gio *vself,
220 597250 : void *buf,
221 597250 : size_t count) {
222 597250 : return NaClGioShmReadOrWrite(vself, buf, count, 0);
223 : }
224 :
225 400642 : static ssize_t NaClGioShmWrite(struct Gio *vself,
226 400642 : const void *buf,
227 400642 : size_t count) {
228 400642 : return NaClGioShmReadOrWrite(vself, (void *) buf, count, 1);
229 : }
230 :
231 1974308 : static off_t NaClGioShmSeek(struct Gio *vself,
232 1974308 : off_t offset,
233 1974308 : int whence) {
234 1974308 : struct NaClGioShm *self = (struct NaClGioShm *) vself;
235 1974308 : size_t new_pos = (size_t) -1;
236 :
237 1974308 : NaClLog(4, "NaClGioShmSeek(0x%"NACL_PRIxPTR", %ld (0x%lx), %d)\n",
238 : (uintptr_t) vself, (long) offset, (long) offset, whence);
239 : /*
240 : * Note that if sizeof(new_pos) < sizeof(offset), we are dropping
241 : * high-order bits and we do not detect this. However, the check
242 : * after the switch keeps the values somewhat sane: we will never
243 : * set the I/O offset to be outside the range [0, self->shm_sz].
244 : */
245 1974308 : switch (whence) {
246 : case SEEK_SET:
247 987150 : new_pos = (size_t) offset;
248 987150 : break;
249 : case SEEK_CUR:
250 4 : new_pos = self->io_offset + offset;
251 4 : break;
252 : case SEEK_END:
253 0 : new_pos = self->shm_sz + offset;
254 0 : break;
255 : }
256 : /* allow equality, so setting to the end of file is okay */
257 987154 : if (self->shm_sz < new_pos) {
258 0 : NaClLog(4, " invalid offset\n");
259 0 : errno = EINVAL;
260 0 : return -1;
261 : }
262 987154 : NaClLog(4, " setting to %ld (0x%lx)\n", (long) new_pos, (long) new_pos);
263 : /* sizeof(off_t) >= sizeof(size_t) */
264 987154 : self->io_offset = new_pos;
265 987154 : return (off_t) self->io_offset;
266 987154 : }
267 :
268 0 : static int NaClGioShmFlush(struct Gio *vself) {
269 0 : UNREFERENCED_PARAMETER(vself);
270 0 : return 0;
271 : }
272 :
273 6 : static int NaClGioShmClose(struct Gio *vself) {
274 6 : struct NaClGioShm *self = (struct NaClGioShm *) vself;
275 :
276 6 : if (NULL != self->cur_window) {
277 6 : NaClDescUnmapUnsafe(self->shmp, (void *) self->cur_window,
278 : NACL_MAP_PAGESIZE);
279 6 : }
280 6 : self->cur_window = NULL;
281 :
282 6 : if (NULL == self->shmp) {
283 0 : NaClLog(LOG_ERROR, "NaClGioShmClose: double close detected\n");
284 0 : errno = EIO;
285 0 : return -1;
286 : }
287 :
288 6 : NaClDescUnref(self->shmp);
289 6 : self->shmp = NULL; /* double close will fault */
290 6 : return 0;
291 6 : }
292 :
293 6 : static void NaClGioShmDtor(struct Gio *vself) {
294 6 : struct NaClGioShm *self = (struct NaClGioShm *) vself;
295 :
296 : /*
297 : * Users of Gio objects are expected to Close then Dtor, but Dtor
298 : * should cleanup regardless.
299 : */
300 6 : if (NULL != self->shmp) {
301 1 : if (-1 == (*vself->vtbl->Close)(vself)) {
302 0 : NaClLog(LOG_ERROR, "NaClGioShmDtor: auto Close failed!\n");
303 0 : }
304 1 : }
305 :
306 6 : self->shmp = NULL;
307 6 : self->base.vtbl = NULL;
308 6 : }
309 :
310 : const struct GioVtbl kNaClGioShmVtbl = {
311 : NaClGioShmDtor,
312 : NaClGioShmRead,
313 : NaClGioShmWrite,
314 : NaClGioShmSeek,
315 : NaClGioShmFlush,
316 : NaClGioShmClose,
317 : };
318 :
319 :
320 6 : static int NaClGioShmCtorIntern(struct NaClGioShm *self,
321 6 : struct NaClDesc *shmp,
322 6 : size_t shm_size) {
323 6 : struct nacl_abi_stat stbuf;
324 6 : int vfret;
325 6 : int rval = 0;
326 :
327 6 : self->base.vtbl = NULL;
328 :
329 6 : self->shmp = NULL;
330 6 : self->cur_window = NULL;
331 :
332 6 : if (0 != (vfret = (*((struct NaClDescVtbl const *) shmp->base.vtbl)->
333 : Fstat)(shmp, &stbuf))) {
334 0 : NaClLog(1, "NaClGioShmCtorIntern: Fstat virtual function returned %d\n",
335 : vfret);
336 0 : goto cleanup;
337 : }
338 : /*
339 : * nacl_abi_off_t is signed 32-bit quantity, but we don't want to
340 : * hardwire in that knowledge here.
341 : *
342 : * size_t is unsigned, and may be 32-bits or 64-bits, depending on
343 : * the underlying host OS.
344 : *
345 : * we want to ensure that the shm's size, as reported by the desc
346 : * abstraction and thus is in nacl_abi_off_t, is at least that
347 : * claimed by the ctor argument. so, if (as Integers)
348 : *
349 : * stbuf.nacl_abi_st_size < shm_size
350 : *
351 : * holds, this is an error. however, the value-preserving cast rule
352 : * makes this harder.
353 : *
354 : * Note that for signed sizes (ssize_t), the kernel ABI generally
355 : * only reserve -1 for error, and asking for an I/O operation via a
356 : * size_t that would succeed but yield a ssize_t return value that
357 : * is negative is okay, since -1 is never valid as an I/O size on a
358 : * von Neuman machine (except for a writev where the iov entries
359 : * overlap): there just isn't that much data to read/write, when the
360 : * instructions also take up space in the process address space.
361 : * Whether requiring the programmer to detect this corner case is
362 : * advisable is a different argument -- similar to negative ssize_t
363 : * sizes, the syscall can just succeed with a partial transfer to
364 : * avoid returning -1 on a success, just as we could avoid returning
365 : * negative values; in practice, we do the latter, since we often
366 : * see code written that tests for syscall error by comparing the
367 : * return value to see if it is less than zero, rather than if it is
368 : * equal to -1.
369 : */
370 6 : if (stbuf.nacl_abi_st_size < 0) {
371 0 : NaClLog(LOG_ERROR,
372 : ("NaClGioShmCtorIntern: actual shm size negative"
373 : " %"NACL_PRIdNACL_OFF"\n"),
374 : stbuf.nacl_abi_st_size);
375 0 : goto cleanup;
376 : }
377 6 : if (stbuf.nacl_abi_st_size <= (nacl_abi_off_t) SIZE_T_MAX
378 : && (size_t) stbuf.nacl_abi_st_size < shm_size) {
379 0 : NaClLog(LOG_ERROR,
380 : ("NaClGioShmCtorIntern: claimed shm file size greater than"
381 : " actual shm segment size, %"NACL_PRIuS" vs"
382 : " %"NACL_PRIuNACL_OFF"\n"),
383 : shm_size,
384 : stbuf.nacl_abi_st_size);
385 0 : goto cleanup;
386 : }
387 6 : if (OFF_T_MAX < SIZE_T_MAX && (size_t) OFF_T_MAX < shm_size) {
388 0 : NaClLog(LOG_ERROR,
389 : ("NaClGioShmCtorIntern: claimed shm file size greater than"
390 : " off_t max value, %"NACL_PRId64"\n"),
391 : (int64_t) OFF_T_MAX);
392 0 : goto cleanup;
393 : }
394 :
395 6 : self->shmp = NaClDescRef(shmp);
396 :
397 6 : self->io_offset = 0;
398 6 : self->shm_sz = shm_size;
399 6 : self->window_offset = 0;
400 :
401 6 : self->base.vtbl = &kNaClGioShmVtbl;
402 :
403 6 : if (!NaClGioShmSetWindow(self, 0)) {
404 0 : NaClLog(LOG_ERROR,
405 : ("NaClGioShmCtorIntern: initial seek to beginning failed\n"));
406 0 : NaClDescUnref(self->shmp);
407 0 : self->shmp = NULL;
408 0 : self->shm_sz = 0;
409 0 : self->base.vtbl = NULL;
410 0 : goto cleanup;
411 : }
412 :
413 6 : rval = 1;
414 : cleanup:
415 6 : return rval;
416 : }
417 :
418 1 : int NaClGioShmCtor(struct NaClGioShm *self,
419 1 : struct NaClDesc *shmp,
420 1 : size_t shm_size) {
421 :
422 1 : int rv;
423 :
424 3 : CHECK(shm_size == NaClRoundAllocPage(shm_size));
425 :
426 1 : rv = NaClGioShmCtorIntern(self, shmp, shm_size);
427 :
428 1 : return rv;
429 : }
430 :
431 5 : int NaClGioShmAllocCtor(struct NaClGioShm *self,
432 5 : size_t shm_size) {
433 5 : struct NaClDescImcShm *shmp;
434 5 : int rv;
435 :
436 15 : CHECK(shm_size == NaClRoundAllocPage(shm_size));
437 :
438 5 : shmp = malloc(sizeof *shmp);
439 5 : if (NULL == shmp) {
440 0 : return 0;
441 : }
442 5 : if (!NaClDescImcShmAllocCtor(shmp, shm_size, /* executable= */ 0)) {
443 0 : free(shmp);
444 0 : return 0;
445 : }
446 :
447 5 : rv = NaClGioShmCtorIntern(self, (struct NaClDesc *) shmp, shm_size);
448 5 : NaClDescUnref((struct NaClDesc *) shmp);
449 :
450 5 : if (!rv) {
451 0 : free(shmp);
452 0 : }
453 5 : return rv;
454 5 : }
|