1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ppapi/tests/test_broker.h"
6
7#if defined(_MSC_VER)
8#define OS_WIN 1
9#include <windows.h>
10#else
11#define OS_POSIX 1
12#include <errno.h>
13#include <unistd.h>
14#endif
15
16#include <cstdio>
17#include <cstring>
18#include <fstream>
19#include <limits>
20
21#include "ppapi/c/pp_errors.h"
22#include "ppapi/c/trusted/ppp_broker.h"
23#include "ppapi/c/trusted/ppb_broker_trusted.h"
24#include "ppapi/tests/test_utils.h"
25#include "ppapi/tests/testing_instance.h"
26
27REGISTER_TEST_CASE(Broker);
28
29namespace {
30
31const char kHelloMessage[] = "Hello Plugin! This is Broker!";
32// Message sent from broker to plugin if the broker is unsandboxed.
33const char kBrokerUnsandboxed[] = "Broker is Unsandboxed!";
34// Message sent from broker to plugin if the broker is sandboxed. This message
35// needs to be longer than |kBrokerUnsandboxed| because the plugin is expecting
36// |kBrokerUnsandboxed|. If it's shorter and the broker doesn't close its handle
37// properly the plugin will hang waiting for all data of |kBrokerUnsandboxed| to
38// be read.
39const char kBrokerSandboxed[] = "Broker is Sandboxed! Verification failed!";
40
41#if defined(OS_WIN)
42typedef HANDLE PlatformFile;
43const PlatformFile kInvalidPlatformFileValue = INVALID_HANDLE_VALUE;
44const int32_t kInvalidHandle = static_cast<int32_t>(
45    reinterpret_cast<intptr_t>(INVALID_HANDLE_VALUE));
46#elif defined(OS_POSIX)
47typedef int PlatformFile;
48const PlatformFile kInvalidPlatformFileValue = -1;
49const int32_t kInvalidHandle = -1;
50#endif
51
52PlatformFile IntToPlatformFile(int32_t handle) {
53#if defined(OS_WIN)
54  return reinterpret_cast<HANDLE>(static_cast<intptr_t>(handle));
55#elif defined(OS_POSIX)
56  return handle;
57#endif
58}
59
60#if defined(OS_POSIX)
61
62#define HANDLE_EINTR(x) ({ \
63  typeof(x) eintr_wrapper_result; \
64  do { \
65    eintr_wrapper_result = (x); \
66  } while (eintr_wrapper_result == -1 && errno == EINTR); \
67  eintr_wrapper_result; \
68})
69
70#define IGNORE_EINTR(x) ({ \
71  typeof(x) eintr_wrapper_result; \
72  do { \
73    eintr_wrapper_result = (x); \
74    if (eintr_wrapper_result == -1 && errno == EINTR) { \
75      eintr_wrapper_result = 0; \
76    } \
77  } while (0); \
78  eintr_wrapper_result; \
79})
80
81#endif
82
83bool ReadMessage(PlatformFile file, size_t message_len, char* message) {
84#if defined(OS_WIN)
85  assert(message_len < std::numeric_limits<DWORD>::max());
86  DWORD read = 0;
87  const DWORD size = static_cast<DWORD>(message_len);
88  return ::ReadFile(file, message, size, &read, NULL) && read == size;
89#elif defined(OS_POSIX)
90  assert(message_len <
91         static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
92  size_t total_read = 0;
93  while (total_read < message_len) {
94    ssize_t read = HANDLE_EINTR(::read(file, message + total_read,
95                                       message_len - total_read));
96    if (read <= 0)
97      break;
98    total_read += read;
99  }
100  return total_read == message_len;
101#endif
102}
103
104bool WriteMessage(PlatformFile file, size_t message_len, const char* message) {
105#if defined(OS_WIN)
106  assert(message_len < std::numeric_limits<DWORD>::max());
107  DWORD written = 0;
108  const DWORD size = static_cast<DWORD>(message_len);
109  return ::WriteFile(file, message, size, &written, NULL) && written == size;
110#elif defined(OS_POSIX)
111  assert(message_len <
112         static_cast<size_t>(std::numeric_limits<ssize_t>::max()));
113  size_t total_written = 0;
114  while (total_written < message_len) {
115    ssize_t written = HANDLE_EINTR(::write(file, message + total_written,
116                                           message_len - total_written));
117    if (written <= 0)
118      break;
119    total_written += written;
120  }
121  return total_written == message_len;
122#endif
123}
124
125bool VerifyMessage(PlatformFile file, size_t message_len, const char* message) {
126  char* message_received = new char[message_len];
127  bool success = ReadMessage(file, message_len, message_received) &&
128                 !::strcmp(message_received, message);
129  delete [] message_received;
130  return success;
131}
132
133bool ClosePlatformFile(PlatformFile file) {
134#if defined(OS_WIN)
135  return !!::CloseHandle(file);
136#elif defined(OS_POSIX)
137  return !IGNORE_EINTR(::close(file));
138#endif
139}
140
141bool VerifyIsUnsandboxed() {
142#if defined(OS_WIN)
143  FILE* file = NULL;
144  wchar_t temp_path[MAX_PATH] = {'\0'};
145  wchar_t file_name[MAX_PATH] = {'\0'};
146  if (!::GetTempPath(MAX_PATH, temp_path) ||
147      !::GetTempFileName(temp_path, L"test_pepper_broker", 0, file_name) ||
148      ::_wfopen_s(&file, file_name, L"w"))
149    return false;
150
151  if (::fclose(file)) {
152    ::DeleteFile(file_name);
153    return false;
154  }
155
156  return !!::DeleteFile(file_name);
157#elif defined(OS_POSIX)
158  char file_name[] = "/tmp/test_pepper_broker_XXXXXX";
159  int fd = ::mkstemp(file_name);
160  if (-1 == fd)
161    return false;
162
163  if (IGNORE_EINTR(::close(fd))) {
164    ::remove(file_name);
165    return false;
166  }
167
168  return !::remove(file_name);
169#endif
170}
171
172// Callback in the broker when a new broker connection occurs.
173int32_t OnInstanceConnected(PP_Instance instance, int32_t handle) {
174  PlatformFile file = IntToPlatformFile(handle);
175  if (file == kInvalidPlatformFileValue)
176    return PP_ERROR_FAILED;
177
178  // Send hello message.
179  if (!WriteMessage(file, sizeof(kHelloMessage), kHelloMessage)) {
180    ClosePlatformFile(file);
181    return PP_ERROR_FAILED;
182  }
183
184  // Verify broker is not sandboxed and send result to plugin over the pipe.
185  if (VerifyIsUnsandboxed()) {
186    if (!WriteMessage(file, sizeof(kBrokerUnsandboxed), kBrokerUnsandboxed)) {
187      ClosePlatformFile(file);
188      return PP_ERROR_FAILED;
189    }
190  } else {
191    if (!WriteMessage(file, sizeof(kBrokerSandboxed), kBrokerSandboxed)) {
192      ClosePlatformFile(file);
193      return PP_ERROR_FAILED;
194    }
195  }
196
197  if (!ClosePlatformFile(file))
198    return PP_ERROR_FAILED;
199
200  return PP_OK;
201}
202
203}  // namespace
204
205PP_EXPORT int32_t PPP_InitializeBroker(
206    PP_ConnectInstance_Func* connect_instance_func) {
207  *connect_instance_func = &OnInstanceConnected;
208  return PP_OK;
209}
210
211PP_EXPORT void PPP_ShutdownBroker() {}
212
213TestBroker::TestBroker(TestingInstance* instance)
214    : TestCase(instance),
215      broker_interface_(NULL) {
216}
217
218bool TestBroker::Init() {
219  broker_interface_ = static_cast<const PPB_BrokerTrusted*>(
220      pp::Module::Get()->GetBrowserInterface(PPB_BROKER_TRUSTED_INTERFACE));
221  return !!broker_interface_;
222}
223
224void TestBroker::RunTests(const std::string& filter) {
225  RUN_TEST(Create, filter);
226  RUN_TEST(Create, filter);
227  RUN_TEST(GetHandleFailure, filter);
228  RUN_TEST_FORCEASYNC_AND_NOT(ConnectFailure, filter);
229  RUN_TEST_FORCEASYNC_AND_NOT(ConnectAndPipe, filter);
230
231  // The following tests require special setup, so only run them if they're
232  // explicitly specified by the filter.
233  if (!ShouldRunAllTests(filter)) {
234    RUN_TEST(ConnectPermissionDenied, filter);
235    RUN_TEST(ConnectPermissionGranted, filter);
236    RUN_TEST(IsAllowedPermissionDenied, filter);
237    RUN_TEST(IsAllowedPermissionGranted, filter);
238  }
239}
240
241std::string TestBroker::TestCreate() {
242  // Very simplistic test to make sure we can create a broker interface.
243  // TODO(raymes): All of the resources created in this file are leaked. Write
244  // a C++ wrapper for PPB_Broker_Trusted to avoid these leaks.
245  PP_Resource broker = broker_interface_->CreateTrusted(
246      instance_->pp_instance());
247  ASSERT_TRUE(broker);
248
249  ASSERT_FALSE(broker_interface_->IsBrokerTrusted(0));
250  ASSERT_TRUE(broker_interface_->IsBrokerTrusted(broker));
251
252  PASS();
253}
254
255// Test connection on invalid resource.
256std::string TestBroker::TestConnectFailure() {
257  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
258  callback.WaitForResult(broker_interface_->Connect(0,
259      callback.GetCallback().pp_completion_callback()));
260  CHECK_CALLBACK_BEHAVIOR(callback);
261  ASSERT_EQ(PP_ERROR_BADRESOURCE, callback.result());
262
263  PASS();
264}
265
266std::string TestBroker::TestGetHandleFailure() {
267  int32_t handle = kInvalidHandle;
268
269  // Test getting the handle for an invalid resource.
270  ASSERT_EQ(PP_ERROR_BADRESOURCE, broker_interface_->GetHandle(0, &handle));
271
272  // Connect hasn't been called so this should fail.
273  PP_Resource broker = broker_interface_->CreateTrusted(
274      instance_->pp_instance());
275  ASSERT_TRUE(broker);
276  ASSERT_EQ(PP_ERROR_FAILED, broker_interface_->GetHandle(broker, &handle));
277
278  PASS();
279}
280
281std::string TestBroker::TestConnectAndPipe() {
282  PP_Resource broker = broker_interface_->CreateTrusted(
283      instance_->pp_instance());
284  ASSERT_TRUE(broker);
285
286  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
287  callback.WaitForResult(broker_interface_->Connect(broker,
288      callback.GetCallback().pp_completion_callback()));
289  CHECK_CALLBACK_BEHAVIOR(callback);
290  ASSERT_EQ(PP_OK, callback.result());
291
292  int32_t handle = kInvalidHandle;
293  ASSERT_EQ(PP_OK, broker_interface_->GetHandle(broker, &handle));
294  ASSERT_NE(kInvalidHandle, handle);
295
296  PlatformFile file = IntToPlatformFile(handle);
297  ASSERT_TRUE(VerifyMessage(file, sizeof(kHelloMessage), kHelloMessage));
298  ASSERT_TRUE(VerifyMessage(file, sizeof(kBrokerUnsandboxed),
299                            kBrokerUnsandboxed));
300
301  ASSERT_TRUE(ClosePlatformFile(file));
302
303  PASS();
304}
305
306std::string TestBroker::TestConnectPermissionDenied() {
307  // This assumes that the browser side is set up to deny access to the broker.
308  PP_Resource broker = broker_interface_->CreateTrusted(
309      instance_->pp_instance());
310  ASSERT_TRUE(broker);
311
312  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
313  callback.WaitForResult(broker_interface_->Connect(broker,
314      callback.GetCallback().pp_completion_callback()));
315  CHECK_CALLBACK_BEHAVIOR(callback);
316  ASSERT_EQ(PP_ERROR_NOACCESS, callback.result());
317
318  PASS();
319}
320
321std::string TestBroker::TestConnectPermissionGranted() {
322  // This assumes that the browser side is set up to allow access to the broker.
323  PP_Resource broker = broker_interface_->CreateTrusted(
324      instance_->pp_instance());
325  ASSERT_TRUE(broker);
326
327  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
328  callback.WaitForResult(broker_interface_->Connect(broker,
329      callback.GetCallback().pp_completion_callback()));
330  CHECK_CALLBACK_BEHAVIOR(callback);
331  ASSERT_EQ(PP_OK, callback.result());
332
333  PASS();
334}
335
336std::string TestBroker::TestIsAllowedPermissionDenied() {
337  PP_Resource broker = broker_interface_->CreateTrusted(
338      instance_->pp_instance());
339  ASSERT_TRUE(broker);
340  ASSERT_EQ(PP_FALSE, broker_interface_->IsAllowed(broker));
341
342  PASS();
343}
344
345std::string TestBroker::TestIsAllowedPermissionGranted() {
346  PP_Resource broker = broker_interface_->CreateTrusted(
347      instance_->pp_instance());
348  ASSERT_TRUE(broker);
349  ASSERT_EQ(PP_TRUE, broker_interface_->IsAllowed(broker));
350
351  PASS();
352}
353