diff options
Diffstat (limited to 'sandbox/src/ipc_unittest.cc')
-rw-r--r-- | sandbox/src/ipc_unittest.cc | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/sandbox/src/ipc_unittest.cc b/sandbox/src/ipc_unittest.cc new file mode 100644 index 0000000..10c3fde --- /dev/null +++ b/sandbox/src/ipc_unittest.cc @@ -0,0 +1,454 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "sandbox/src/crosscall_client.h" +#include "sandbox/src/crosscall_server.h" +#include "sandbox/src/sharedmem_ipc_client.h" +#include "testing/gtest/include/gtest/gtest.h" + + +namespace sandbox { + +// Helper function to make the fake shared memory with some +// basic elements initialized. +IPCControl* MakeChannels(size_t channel_size, size_t total_shared_size, + size_t* base_start) { + // Allocate memory + char* mem = new char[total_shared_size]; + memset(mem, 0, total_shared_size); + + // Calculate how many channels we can fit in the shared memory. + total_shared_size -= offsetof(IPCControl, channels); + size_t channel_count = + total_shared_size / (sizeof(ChannelControl) + channel_size); + + // Calculate the start of the first channel. + *base_start = (sizeof(ChannelControl)* channel_count) + + offsetof(IPCControl, channels); + + // Setup client structure. + IPCControl* client_control = reinterpret_cast<IPCControl*>(mem); + client_control->channels_count = channel_count; + + return client_control; +} + +TEST(IPCTest, ChannelMaker) { + size_t channel_start = 0; + IPCControl* client_control = MakeChannels(12*64, 4096, &channel_start); + + // Test that our testing rig is computing offsets properly. We should have + // 5 channnels and the offset to the first channel is 108 bytes. + ASSERT_TRUE(NULL != client_control); + EXPECT_EQ(5, client_control->channels_count); + EXPECT_EQ(108, channel_start); + + delete [] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, ClientLockUnlock) { + // Make 7 channels of kIPCChannelSize (1kb) each. Test that we lock and + // unlock channels properly. + const size_t channel_size = kIPCChannelSize; + size_t base_start = 0; + IPCControl* client_control = MakeChannels(channel_size, 4096*2, &base_start); + + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + channel.channel_base = base_start; + channel.state = kFreeChannel; + base_start += channel_size; + } + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + // Test that we lock the first 3 channels in sequence. + void* buff0 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[0].channel_base == buff0); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff1 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff2 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[2].channel_base == buff2); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + // Test that we unlock and re-lock the right channel. + client.FreeBuffer(buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + void* buff2b = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff2b); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + client.FreeBuffer(buff0); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kBusyChannel, client_control->channels[2].state); + EXPECT_EQ(kFreeChannel, client_control->channels[3].state); + EXPECT_EQ(kFreeChannel, client_control->channels[4].state); + EXPECT_EQ(kFreeChannel, client_control->channels[5].state); + + delete [] reinterpret_cast<char*>(client_control); +} + +TEST(IPCTest, CrossCallStrPacking) { + // This test tries the CrossCall object with null and non-null string + // combination of parameters and verifies that the unpacker can read them + // properly. + const size_t channel_size = kIPCChannelSize; + size_t base_start = 0; + IPCControl* client_control = MakeChannels(channel_size, 4096*2, &base_start); + client_control->server_alive = HANDLE(1); + + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + channel.channel_base = base_start; + channel.state = kFreeChannel; + channel.ping_event = ::CreateEventW(NULL, FALSE, FALSE, NULL); + channel.pong_event = ::CreateEventW(NULL, FALSE, TRUE, NULL); + base_start += channel_size; + } + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + CrossCallReturn answer; + uint32 tag1 = 666; + const wchar_t text[] = L"98765 - 43210"; + std::wstring copied_text; + CrossCallParamsEx* actual_params; + + CrossCall(client, tag1, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + EXPECT_STREQ(text, copied_text.c_str()); + + // Check with an empty string. + uint32 tag2 = 777; + const wchar_t* null_text = NULL; + CrossCall(client, tag2, null_text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(1, actual_params->GetParamsCount()); + EXPECT_EQ(tag2, actual_params->GetTag()); + size_t param_size = 1; + ArgType type = INVALID_TYPE; + void* param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + EXPECT_TRUE(NULL != param_addr); + EXPECT_EQ(0, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + + uint32 tag3 = 888; + param_size = 1; + copied_text.clear(); + + // Check with an empty string and a non-empty string. + CrossCall(client, tag3, null_text, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(2, actual_params->GetParamsCount()); + EXPECT_EQ(tag3, actual_params->GetTag()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(0, ¶m_size, &type); + EXPECT_TRUE(NULL != param_addr); + EXPECT_EQ(0, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text)); + EXPECT_TRUE(actual_params->GetParameterStr(1, &copied_text)); + EXPECT_STREQ(text, copied_text.c_str()); + + param_size = 1; + std::wstring copied_text_p0, copied_text_p2; + + const wchar_t text2[] = L"AeFG"; + CrossCall(client, tag1, text2, null_text, text, &answer); + actual_params = reinterpret_cast<CrossCallParamsEx*>(client.GetBuffer()); + EXPECT_EQ(3, actual_params->GetParamsCount()); + EXPECT_EQ(tag1, actual_params->GetTag()); + EXPECT_TRUE(actual_params->GetParameterStr(0, &copied_text_p0)); + EXPECT_STREQ(text2, copied_text_p0.c_str()); + EXPECT_TRUE(actual_params->GetParameterStr(2, &copied_text_p2)); + EXPECT_STREQ(text, copied_text_p2.c_str()); + type = INVALID_TYPE; + param_addr = actual_params->GetRawParameter(1, ¶m_size, &type); + EXPECT_TRUE(NULL != param_addr); + EXPECT_EQ(0, param_size); + EXPECT_EQ(WCHAR_TYPE, type); + + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + ::CloseHandle(channel.ping_event); + ::CloseHandle(channel.pong_event); + } + delete [] reinterpret_cast<char*>(client_control); +} + +// This structure is passed to the mock server threads to simulate +// the server side IPC so it has the required kernel objects. +struct ServerEvents { + HANDLE ping; + HANDLE pong; + volatile LONG* state; + HANDLE mutex; +}; + +// This is the server thread that quicky answers an IPC and exits. +DWORD WINAPI QuickResponseServer(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->ping, INFINITE); + ::InterlockedExchange(events->state, kAckChannel); + ::SetEvent(events->pong); + return wait_result; +} + +class CrossCallParamsMock : public CrossCallParams { + public: + CrossCallParamsMock(uint32 tag, size_t params_count) + : CrossCallParams(tag, params_count) { + } + private: + void* params[4]; +}; + +void FakeOkAnswerInChannel(void* channel) { + CrossCallReturn* answer = reinterpret_cast<CrossCallReturn*>(channel); + answer->call_outcome = SBOX_ALL_OK; +} + +// Create two threads that will quickly answer IPCs; the first one +// using channel 1 (channel 0 is busy) and one using channel 0. No time-out +// should occur. +TEST(IPCTest, ClientFastServer) { + const size_t channel_size = kIPCChannelSize; + size_t base_start = 0; + IPCControl* client_control = MakeChannels(channel_size, 4096*2, &base_start); + + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + channel.channel_base = base_start; + channel.state = kFreeChannel; + channel.ping_event = ::CreateEventW(NULL, FALSE, FALSE, NULL); + channel.pong_event = ::CreateEventW(NULL, FALSE, FALSE, NULL); + base_start += channel_size; + } + + client_control->server_alive = ::CreateMutex(NULL, FALSE, NULL); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + ServerEvents events = {0}; + events.ping = client_control->channels[1].ping_event; + events.pong = client_control->channels[1].pong_event; + events.state = &client_control->channels[1].state; + + HANDLE t1 = ::CreateThread(NULL, 0, QuickResponseServer, &events, 0, NULL); + ASSERT_TRUE(NULL != t1); + ::CloseHandle(t1); + + void* buff0 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[0].channel_base == buff0); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + void* buff1 = client.GetBuffer(); + EXPECT_TRUE(mem + client_control->channels[1].channel_base == buff1); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kBusyChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + EXPECT_EQ(0, client_control->channels[1].ipc_tag); + + uint32 tag = 7654; + CrossCallReturn answer; + CrossCallParamsMock* params1 = new(buff1) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff1); + + ResultCode result = client.DoCall(params1, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff1); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[1].ipc_tag); + EXPECT_EQ(kBusyChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + HANDLE t2 = ::CreateThread(NULL, 0, QuickResponseServer, &events, 0, NULL); + ASSERT_TRUE(NULL != t2); + ::CloseHandle(t2); + + client.FreeBuffer(buff0); + events.ping = client_control->channels[0].ping_event; + events.pong = client_control->channels[0].pong_event; + events.state = &client_control->channels[0].state; + + tag = 4567; + CrossCallParamsMock* params2 = new(buff0) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff0); + + result = client.DoCall(params2, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff0); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[0].ipc_tag); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + EXPECT_EQ(kFreeChannel, client_control->channels[1].state); + EXPECT_EQ(kFreeChannel, client_control->channels[2].state); + + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + ::CloseHandle(channel.ping_event); + ::CloseHandle(channel.pong_event); + } + + ::CloseHandle(client_control->server_alive); + delete [] reinterpret_cast<char*>(client_control); +} + +// This is the server thread that very slowly answers an IPC and exits. Note +// that the pong event needs to be signaled twice. +DWORD WINAPI SlowResponseServer(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->ping, INFINITE); + ::Sleep(kIPCWaitTimeOut1 + kIPCWaitTimeOut2 + 200); + ::InterlockedExchange(events->state, kAckChannel); + ::SetEvent(events->pong); + return wait_result; +} + +// This thread's job is to keep the mutex locked. +DWORD WINAPI MainServerThread(PVOID param) { + ServerEvents* events = reinterpret_cast<ServerEvents*>(param); + DWORD wait_result = 0; + wait_result = ::WaitForSingleObject(events->mutex, INFINITE); + Sleep(kIPCWaitTimeOut1 * 20); + return wait_result; +} + +// Creates a server thread that answers the IPC so slow that is guaranteed to +// trigger the time-out code path in the client. A second thread is created +// to hold locked the server_alive mutex: this signals the client that the +// server is not dead and it retries the wait. +TEST(IPCTest, ClientSlowServer) { + const size_t channel_size = kIPCChannelSize; + size_t base_start = 0; + IPCControl* client_control = MakeChannels(channel_size, 4096*2, &base_start); + + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + channel.channel_base = base_start; + channel.state = kFreeChannel; + channel.ping_event = ::CreateEventW(NULL, FALSE, FALSE, NULL); + channel.pong_event = ::CreateEventW(NULL, FALSE, FALSE, NULL); + base_start += channel_size; + } + + client_control->server_alive = ::CreateMutex(NULL, FALSE, NULL); + + char* mem = reinterpret_cast<char*>(client_control); + SharedMemIPCClient client(mem); + + ServerEvents events = {0}; + events.ping = client_control->channels[0].ping_event; + events.pong = client_control->channels[0].pong_event; + events.state = &client_control->channels[0].state; + + HANDLE t1 = ::CreateThread(NULL, 0, SlowResponseServer, &events, 0, NULL); + ASSERT_TRUE(NULL != t1); + ::CloseHandle(t1); + + ServerEvents events2 = {0}; + events2.pong = events.pong; + events2.mutex = client_control->server_alive; + + HANDLE t2 = ::CreateThread(NULL, 0, MainServerThread, &events2, 0, NULL); + ASSERT_TRUE(NULL != t2); + ::CloseHandle(t2); + + ::Sleep(1); + + void* buff0 = client.GetBuffer(); + uint32 tag = 4321; + CrossCallReturn answer; + CrossCallParamsMock* params1 = new(buff0) CrossCallParamsMock(tag, 1); + FakeOkAnswerInChannel(buff0); + + ResultCode result = client.DoCall(params1, &answer); + if (SBOX_ERROR_CHANNEL_ERROR != result) + client.FreeBuffer(buff0); + + EXPECT_TRUE(SBOX_ALL_OK == result); + EXPECT_EQ(tag, client_control->channels[0].ipc_tag); + EXPECT_EQ(kFreeChannel, client_control->channels[0].state); + + for (size_t ix = 0; ix != client_control->channels_count; ++ix) { + ChannelControl& channel = client_control->channels[ix]; + ::CloseHandle(channel.ping_event); + ::CloseHandle(channel.pong_event); + } + ::CloseHandle(client_control->server_alive); + delete [] reinterpret_cast<char*>(client_control); +} + +} // namespace sandbox |