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 : /* NaCl inter-module communication primitives. */
9 :
10 : /* Disables the generation of the min and max macro in <windows.h> */
11 : #ifndef NOMINMAX
12 : #define NOMINMAX
13 : #endif
14 :
15 : #include <algorithm>
16 : #include <ctype.h>
17 : #include <limits.h>
18 : #include <stdio.h>
19 : #include <string>
20 : #include <windows.h>
21 : #include <sys/types.h>
22 :
23 : #include "native_client/src/include/atomic_ops.h"
24 : #include "native_client/src/include/portability.h"
25 : #include "native_client/src/shared/imc/nacl_imc_c.h"
26 :
27 :
28 : static NaClBrokerDuplicateHandleFunc g_broker_duplicate_handle_func;
29 :
30 0 : void NaClSetBrokerDuplicateHandleFunc(NaClBrokerDuplicateHandleFunc func) {
31 0 : g_broker_duplicate_handle_func = func;
32 0 : }
33 :
34 : /* Duplicate a Windows HANDLE within the current process. */
35 0 : NaClHandle NaClDuplicateNaClHandle(NaClHandle handle) {
36 : NaClHandle dup_handle;
37 : if (DuplicateHandle(GetCurrentProcess(),
38 : handle,
39 : GetCurrentProcess(),
40 : &dup_handle,
41 : 0,
42 : FALSE,
43 0 : DUPLICATE_SAME_ACCESS)) {
44 0 : return dup_handle;
45 : } else {
46 0 : return NACL_INVALID_HANDLE;
47 : }
48 0 : }
49 :
50 : /*
51 : * This prefix used to be appended to pipe names for pipes
52 : * created in BoundSocket. We keep it for backward compatibility.
53 : * TODO(gregoryd): implement versioning support
54 : */
55 : static const char kOldPipePrefix[] = "\\\\.\\pipe\\google-nacl-";
56 : static const char kPipePrefix[] = "\\\\.\\pipe\\chrome.nacl.";
57 :
58 : static const size_t kPipePrefixSize =
59 : sizeof kPipePrefix / sizeof kPipePrefix[0];
60 : static const size_t kOldPipePrefixSize =
61 : sizeof kOldPipePrefix / sizeof kOldPipePrefix[0];
62 :
63 : static const int kPipePathMax = kPipePrefixSize + NACL_PATH_MAX + 1;
64 : static const int kOutBufferSize = 4096; /* TBD */
65 : static const int kInBufferSize = 4096; /* TBD */
66 : static const int kDefaultTimeoutMilliSeconds = 1000;
67 :
68 : /* ControlHeader::command */
69 : static const int kEchoRequest = 0;
70 : static const int kEchoResponse = 1;
71 : static const int kMessage = 2;
72 : static const int kCancel = 3; /* Cancels Handle transfer operations */
73 :
74 : struct ControlHeader {
75 : int command;
76 : DWORD pid;
77 : uint32_t message_length;
78 : uint32_t handle_count;
79 : };
80 :
81 : /*
82 : * TODO(gregoryd): a similar function exists in Chrome's base, but we cannot
83 : * use it here since it cannot be built with scons.
84 : */
85 7 : static std::wstring ASCIIToWide(const char* ascii) {
86 7 : return std::wstring(ascii, &ascii[strlen(ascii)]);
87 7 : }
88 :
89 3 : static bool GetSocketName(const NaClSocketAddress* address, char* name) {
90 3 : if (address == NULL || !isprint(address->path[0])) {
91 0 : SetLastError(ERROR_INVALID_PARAMETER);
92 0 : return false;
93 : }
94 : sprintf_s(name, kPipePathMax, "%s%.*s",
95 3 : kPipePrefix, NACL_PATH_MAX, address->path);
96 3 : return true;
97 3 : }
98 :
99 : static bool GetSocketNameWithOldPrefix(const NaClSocketAddress* address,
100 1 : char* name) {
101 1 : if (address == NULL || !isprint(address->path[0])) {
102 0 : SetLastError(ERROR_INVALID_PARAMETER);
103 0 : return false;
104 : }
105 : sprintf_s(name, kPipePathMax, "%s%.*s",
106 1 : kOldPipePrefix, NACL_PATH_MAX, address->path);
107 1 : return true;
108 1 : }
109 :
110 5 : static int ReadAll(HANDLE handle, void* buffer, size_t length) {
111 5 : size_t count = 0;
112 5 : while (count < length) {
113 : DWORD len;
114 : DWORD chunk = (DWORD) (
115 5 : ((length - count) <= UINT_MAX) ? (length - count) : UINT_MAX);
116 : if (ReadFile(handle, (char *) buffer + count,
117 5 : chunk, &len, NULL) == FALSE) {
118 2 : return (int) ((0 < count) ? count : -1);
119 : }
120 5 : count += len;
121 5 : }
122 5 : return (int) count;
123 5 : }
124 :
125 5 : static int WriteAll(HANDLE handle, const void* buffer, size_t length) {
126 5 : size_t count = 0;
127 5 : while (count < length) {
128 : DWORD len;
129 : /* The following statement is for the 64 bit portability. */
130 : DWORD chunk = (DWORD) (
131 5 : ((length - count) <= UINT_MAX) ? (length - count) : UINT_MAX);
132 : if (WriteFile(handle, (const char *) buffer + count,
133 5 : chunk, &len, NULL) == FALSE) {
134 1 : return (int) ((0 < count) ? count : -1);
135 : }
136 5 : count += len;
137 5 : }
138 5 : return (int) count;
139 5 : }
140 :
141 0 : static BOOL SkipFile(HANDLE handle, size_t length) {
142 0 : while (0 < length) {
143 : char scratch[1024];
144 0 : size_t count = std::min(sizeof scratch, length);
145 0 : if (ReadAll(handle, scratch, count) != count) {
146 0 : return FALSE;
147 : }
148 0 : length -= count;
149 0 : }
150 0 : return TRUE;
151 0 : }
152 :
153 0 : static BOOL SkipHandles(HANDLE handle, size_t count) {
154 0 : while (0 < count) {
155 : uint64_t discard;
156 0 : if (ReadAll(handle, &discard, sizeof discard) != sizeof discard) {
157 0 : return FALSE;
158 : }
159 0 : CloseHandle((HANDLE) discard);
160 0 : --count;
161 0 : }
162 0 : return TRUE;
163 0 : }
164 :
165 0 : int NaClWouldBlock() {
166 0 : return GetLastError() == ERROR_PIPE_LISTENING;
167 0 : }
168 :
169 3 : NaClHandle NaClBoundSocket(const NaClSocketAddress* address) {
170 : char name[kPipePathMax];
171 3 : if (!GetSocketName(address, name)) {
172 0 : return NACL_INVALID_HANDLE;
173 : }
174 : /* Create a named pipe in nonblocking mode. */
175 : return CreateNamedPipeW(
176 : ASCIIToWide(name).c_str(),
177 : PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
178 : PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT,
179 : PIPE_UNLIMITED_INSTANCES,
180 : kOutBufferSize,
181 : kInBufferSize,
182 : kDefaultTimeoutMilliSeconds,
183 3 : NULL);
184 3 : }
185 :
186 7 : int NaClSocketPair(NaClHandle pair[2]) {
187 : static Atomic32 socket_pair_count;
188 : char name[kPipePathMax];
189 :
190 : do {
191 : sprintf_s(name, kPipePathMax, "%s%u.%lu",
192 : kPipePrefix, GetCurrentProcessId(),
193 7 : AtomicIncrement(&socket_pair_count, 1));
194 : pair[0] = CreateNamedPipeW(
195 : ASCIIToWide(name).c_str(),
196 : PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
197 : PIPE_TYPE_BYTE | PIPE_READMODE_BYTE,
198 : 1,
199 : kOutBufferSize,
200 : kInBufferSize,
201 : kDefaultTimeoutMilliSeconds,
202 7 : NULL);
203 : if (pair[0] == INVALID_HANDLE_VALUE &&
204 : GetLastError() != ERROR_ACCESS_DENIED &&
205 7 : GetLastError() != ERROR_PIPE_BUSY) {
206 0 : return -1;
207 : }
208 7 : } while (pair[0] == INVALID_HANDLE_VALUE);
209 : pair[1] = CreateFileW(ASCIIToWide(name).c_str(),
210 : GENERIC_READ | GENERIC_WRITE,
211 : 0, /* no sharing */
212 : NULL, /* default security attributes */
213 : OPEN_EXISTING, /* opens existing pipe */
214 : SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION,
215 7 : NULL); /* no template file */
216 7 : if (pair[1] == INVALID_HANDLE_VALUE) {
217 0 : CloseHandle(pair[0]);
218 0 : return -1;
219 : }
220 7 : if (ConnectNamedPipe(pair[0], NULL) == FALSE) {
221 7 : DWORD error = GetLastError();
222 7 : if (error != ERROR_PIPE_CONNECTED) {
223 0 : CloseHandle(pair[0]);
224 0 : CloseHandle(pair[1]);
225 0 : return -1;
226 : }
227 : }
228 7 : return 0;
229 7 : }
230 :
231 9 : int NaClClose(NaClHandle handle) {
232 9 : if (handle == NULL || handle == INVALID_HANDLE_VALUE) {
233 0 : return 0;
234 : }
235 9 : return CloseHandle(handle) ? 0 : -1;
236 9 : }
237 :
238 : int NaClSendDatagram(NaClHandle handle, const NaClMessageHeader* message,
239 5 : int flags) {
240 5 : ControlHeader header = { kEchoRequest, GetCurrentProcessId(), 0, 0 };
241 : uint64_t remote_handles[NACL_HANDLE_COUNT_MAX];
242 : uint32_t i;
243 :
244 5 : if (NACL_HANDLE_COUNT_MAX < message->handle_count) {
245 0 : SetLastError(ERROR_INVALID_PARAMETER);
246 0 : return -1;
247 : }
248 5 : if (!NaClMessageSizeIsValid(message)) {
249 0 : SetLastError(ERROR_INVALID_PARAMETER);
250 0 : return -1;
251 : }
252 5 : if (0 < message->handle_count && message->handles) {
253 : HANDLE target;
254 : /*
255 : * TODO(shiki): On Windows Vista, we can use GetNamedPipeClientProcessId()
256 : * and GetNamedPipeServerProcessId() and probably we can remove
257 : * kEchoRequest and kEchoResponse completely.
258 : */
259 : if (WriteAll(handle, &header, sizeof header) != sizeof header ||
260 : ReadAll(handle, &header, sizeof header) != sizeof header ||
261 3 : header.command != kEchoResponse) {
262 0 : return -1;
263 : }
264 3 : if (g_broker_duplicate_handle_func == NULL) {
265 3 : target = OpenProcess(PROCESS_DUP_HANDLE, FALSE, header.pid);
266 3 : if (target == NULL) {
267 0 : return -1;
268 : }
269 : }
270 3 : for (i = 0; i < message->handle_count; ++i) {
271 : HANDLE temp_remote_handle;
272 : bool success;
273 3 : if (g_broker_duplicate_handle_func != NULL) {
274 : success = g_broker_duplicate_handle_func(message->handles[i],
275 : header.pid,
276 : &temp_remote_handle,
277 0 : 0, DUPLICATE_SAME_ACCESS);
278 0 : } else {
279 : success = DuplicateHandle(GetCurrentProcess(), message->handles[i],
280 : target, &temp_remote_handle,
281 3 : 0, FALSE, DUPLICATE_SAME_ACCESS);
282 : }
283 3 : if (!success) {
284 : /*
285 : * Send the kCancel message to revoke the handles duplicated
286 : * so far in the remote peer.
287 : */
288 0 : header.command = kCancel;
289 0 : header.handle_count = i;
290 0 : if (0 < i) {
291 0 : WriteAll(handle, &header, sizeof header);
292 0 : WriteAll(handle, remote_handles, sizeof(uint64_t) * i);
293 : }
294 0 : if (g_broker_duplicate_handle_func == NULL) {
295 0 : CloseHandle(target);
296 : }
297 0 : return -1;
298 : }
299 3 : remote_handles[i] = (uint64_t) temp_remote_handle;
300 3 : }
301 3 : if (g_broker_duplicate_handle_func == NULL) {
302 3 : CloseHandle(target);
303 : }
304 : }
305 5 : header.command = kMessage;
306 5 : header.handle_count = message->handle_count;
307 5 : for (i = 0; i < message->iov_length; ++i) {
308 5 : if (UINT32_MAX - header.message_length < message->iov[i].length) {
309 0 : return -1;
310 : }
311 5 : header.message_length += (uint32_t) message->iov[i].length;
312 5 : }
313 5 : if (WriteAll(handle, &header, sizeof header) != sizeof header) {
314 1 : return -1;
315 : }
316 5 : for (i = 0; i < message->iov_length; ++i) {
317 : if (WriteAll(handle, message->iov[i].base, message->iov[i].length) !=
318 5 : message->iov[i].length) {
319 0 : return -1;
320 : }
321 5 : }
322 : if (0 < message->handle_count && message->handles &&
323 : WriteAll(handle,
324 : remote_handles,
325 : sizeof(uint64_t) * message->handle_count) !=
326 5 : sizeof(uint64_t) * message->handle_count) {
327 0 : return -1;
328 : }
329 5 : return (int) header.message_length;
330 5 : }
331 :
332 : int NaClSendDatagramTo(const NaClMessageHeader* message, int flags,
333 3 : const NaClSocketAddress* name) {
334 : NaClHandle handle;
335 : char pipe_name[kPipePathMax];
336 : int timeout_ms;
337 : int result;
338 :
339 3 : if (NACL_HANDLE_COUNT_MAX < message->handle_count) {
340 0 : SetLastError(ERROR_INVALID_PARAMETER);
341 0 : return -1;
342 : }
343 3 : if (!NaClMessageSizeIsValid(message)) {
344 0 : SetLastError(ERROR_INVALID_PARAMETER);
345 0 : return -1;
346 : }
347 3 : if (!GetSocketName(name, pipe_name)) {
348 0 : return -1;
349 : }
350 3 : timeout_ms = 10;
351 : for (;;) {
352 : handle = CreateFileW(ASCIIToWide(pipe_name).c_str(),
353 : GENERIC_READ | GENERIC_WRITE,
354 : 0, /* no sharing */
355 : NULL, /* default security attributes */
356 : OPEN_EXISTING, /* opens existing pipe */
357 : SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION,
358 3 : NULL); /* no template file */
359 3 : if (handle != INVALID_HANDLE_VALUE) {
360 3 : break;
361 : }
362 :
363 : /* If the pipe is busy it means it exists, so we can try and wait. */
364 1 : if (GetLastError() != ERROR_PIPE_BUSY) {
365 1 : if (GetLastError() != ERROR_FILE_NOT_FOUND) {
366 0 : return -1;
367 : } else {
368 : /*
369 : * Try to find the file using name with prefix (can be created
370 : * by an old version of IMC library.
371 : */
372 1 : if (!GetSocketNameWithOldPrefix(name, pipe_name)) {
373 0 : return -1;
374 : }
375 : handle = CreateFileW(ASCIIToWide(pipe_name).c_str(),
376 : GENERIC_READ | GENERIC_WRITE,
377 : 0, /* no sharing */
378 : NULL, /* default security attributes */
379 : OPEN_EXISTING, /* opens existing pipe */
380 : SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION,
381 1 : NULL); /* no template file */
382 1 : if (handle != INVALID_HANDLE_VALUE) {
383 0 : break;
384 : }
385 1 : if (GetLastError() != ERROR_PIPE_BUSY) {
386 : /* Could not find the pipe - nothing to do */
387 1 : return -1;
388 : }
389 : }
390 0 : break;
391 : }
392 0 : if (flags & NACL_DONT_WAIT) {
393 0 : SetLastError(ERROR_PIPE_LISTENING);
394 0 : return -1;
395 : }
396 : /* Cannot call WaitNamedPipe here because it's blocked by Chrome sandbox. */
397 0 : Sleep(timeout_ms);
398 0 : timeout_ms *= 2;
399 0 : if (timeout_ms > kDefaultTimeoutMilliSeconds) {
400 0 : timeout_ms = kDefaultTimeoutMilliSeconds;
401 : }
402 0 : }
403 3 : result = NaClSendDatagram(handle, message, flags);
404 3 : CloseHandle(handle);
405 3 : return result;
406 3 : }
407 :
408 : static int ReceiveDatagram(NaClHandle handle, NaClMessageHeader* message,
409 5 : int flags, bool bound_socket) {
410 : ControlHeader header;
411 5 : int result = -1;
412 5 : bool dontPeek = false;
413 : uint32_t i;
414 : Repeat:
415 5 : if ((flags & NACL_DONT_WAIT) && !dontPeek) {
416 : DWORD len;
417 : DWORD total;
418 0 : if (PeekNamedPipe(handle, &header, sizeof header, &len, &total, NULL)) {
419 0 : if (len < sizeof header) {
420 0 : SetLastError(ERROR_PIPE_LISTENING);
421 0 : } else {
422 0 : switch (header.command) {
423 : case kEchoRequest:
424 : /*
425 : * Send back the process id to the remote peer to duplicate handles.
426 : * TODO(shiki) : It might be better to keep remote pid by the initial
427 : * handshake rather than send kEchoRequest each time
428 : * before duplicating handles.
429 : */
430 0 : if (ReadAll(handle, &header, sizeof header) == sizeof header) {
431 0 : header.command = kEchoResponse;
432 0 : header.pid = GetCurrentProcessId();
433 0 : WriteAll(handle, &header, sizeof header);
434 0 : if (bound_socket) {
435 : /* We must not close this connection. */
436 0 : dontPeek = true;
437 : }
438 0 : goto Repeat;
439 : }
440 0 : break;
441 : case kEchoResponse:
442 0 : SkipFile(handle, sizeof header);
443 0 : goto Repeat;
444 : break;
445 : case kMessage:
446 0 : if (header.message_length + sizeof header <= total) {
447 0 : if (flags & NACL_DONT_WAIT) {
448 0 : flags &= ~NACL_DONT_WAIT;
449 0 : goto Repeat;
450 : }
451 0 : result = (int) header.message_length;
452 0 : } else {
453 0 : SetLastError(ERROR_PIPE_LISTENING);
454 : }
455 0 : break;
456 : case kCancel:
457 : if (sizeof header + sizeof(uint64_t) * header.handle_count <= len &&
458 0 : ReadAll(handle, &header, sizeof header) == sizeof header) {
459 0 : SkipHandles(handle, header.handle_count);
460 0 : goto Repeat;
461 : }
462 : break;
463 : default:
464 : break;
465 : }
466 : }
467 0 : }
468 5 : } else if (ReadAll(handle, &header, sizeof header) == sizeof header) {
469 5 : dontPeek = false;
470 5 : switch (header.command) {
471 : case kEchoRequest:
472 3 : header.command = kEchoResponse;
473 3 : header.pid = GetCurrentProcessId();
474 3 : WriteAll(handle, &header, sizeof header);
475 3 : goto Repeat;
476 : break;
477 : case kEchoResponse:
478 0 : goto Repeat;
479 : break;
480 : case kMessage: {
481 5 : uint32_t total_message_bytes = header.message_length;
482 5 : size_t count = 0;
483 5 : message->flags = 0;
484 5 : for (i = 0;
485 5 : i < message->iov_length && count < header.message_length;
486 5 : ++i) {
487 5 : NaClIOVec* iov = &message->iov[i];
488 5 : uint32_t len = std::min((uint32_t) iov->length, total_message_bytes);
489 5 : if (ReadAll(handle, iov->base, len) != len) {
490 0 : break;
491 : }
492 5 : total_message_bytes -= len;
493 5 : count += len;
494 5 : }
495 5 : if (count < header.message_length) {
496 0 : if (SkipFile(handle, header.message_length - count) == FALSE) {
497 0 : break;
498 : }
499 0 : message->flags |= NACL_MESSAGE_TRUNCATED;
500 : }
501 5 : if (0 < message->handle_count && message->handles) {
502 : uint64_t received_handles[NACL_HANDLE_COUNT_MAX];
503 : message->handle_count = std::min(message->handle_count,
504 5 : header.handle_count);
505 : if (ReadAll(handle, received_handles,
506 : message->handle_count * sizeof(uint64_t)) !=
507 5 : message->handle_count * sizeof(uint64_t)) {
508 0 : break;
509 : }
510 5 : for (i = 0; i < message->handle_count; ++i) {
511 3 : message->handles[i] = (HANDLE) received_handles[i];
512 5 : }
513 : } else {
514 0 : message->handle_count = 0;
515 : }
516 5 : if (message->handle_count < header.handle_count) {
517 : if (SkipHandles(handle, header.handle_count - message->handle_count) ==
518 0 : FALSE) {
519 0 : break;
520 : }
521 0 : message->flags |= NACL_HANDLES_TRUNCATED;
522 : }
523 5 : result = (int) count;
524 5 : break;
525 : }
526 : case kCancel:
527 0 : SkipHandles(handle, header.handle_count);
528 0 : goto Repeat;
529 : break;
530 : default:
531 : break;
532 : }
533 : }
534 5 : return result;
535 5 : }
536 :
537 : int NaClReceiveDatagram(NaClHandle handle, NaClMessageHeader* message,
538 5 : int flags) {
539 : DWORD state;
540 5 : if (!NaClMessageSizeIsValid(message)) {
541 0 : SetLastError(ERROR_INVALID_PARAMETER);
542 0 : return -1;
543 : }
544 :
545 : /*
546 : * If handle is a bound socket, it is a named pipe in non-blocking mode.
547 : * Set is_bound_socket to true if handle has been created by BoundSocket().
548 : */
549 5 : if (!GetNamedPipeHandleState(handle, &state, NULL, NULL, NULL, NULL, NULL)) {
550 0 : return -1;
551 : }
552 :
553 5 : if (!(state & PIPE_NOWAIT)) {
554 : /* handle is a connected socket. */
555 5 : return ReceiveDatagram(handle, message, flags, false);
556 : }
557 :
558 : /* handle is a bound socket. */
559 : for (;;) {
560 3 : if (ConnectNamedPipe(handle, NULL)) {
561 : /*
562 : * Note ConnectNamedPipe() for a handle in non-blocking mode returns a
563 : * nonzero value just to indicate that the pipe is now available to be
564 : * connected.
565 : */
566 3 : continue;
567 : }
568 3 : switch (GetLastError()) {
569 : case ERROR_PIPE_LISTENING: {
570 : /* Set handle to blocking mode */
571 3 : DWORD mode = PIPE_READMODE_BYTE | PIPE_WAIT;
572 3 : if (flags & NACL_DONT_WAIT) {
573 0 : return -1;
574 : }
575 3 : SetNamedPipeHandleState(handle, &mode, NULL, NULL);
576 3 : break;
577 : }
578 : case ERROR_PIPE_CONNECTED: {
579 : /* Set handle to blocking mode */
580 3 : DWORD mode = PIPE_READMODE_BYTE | PIPE_WAIT;
581 : int result;
582 3 : SetNamedPipeHandleState(handle, &mode, NULL, NULL);
583 3 : result = ReceiveDatagram(handle, message, flags, true);
584 3 : FlushFileBuffers(handle);
585 : /* Set handle back to non-blocking mode. */
586 3 : mode = PIPE_READMODE_BYTE | PIPE_NOWAIT;
587 3 : SetNamedPipeHandleState(handle, &mode, NULL, NULL);
588 3 : DisconnectNamedPipe(handle);
589 3 : if (result == -1 && GetLastError() == ERROR_BROKEN_PIPE) {
590 0 : if (flags & NACL_DONT_WAIT) {
591 0 : SetLastError(ERROR_PIPE_LISTENING);
592 0 : return result;
593 : }
594 0 : } else {
595 3 : return result;
596 : }
597 0 : break;
598 : }
599 : default:
600 0 : return -1;
601 : break;
602 : }
603 3 : }
604 5 : }
|