summaryrefslogtreecommitdiff
path: root/sandbox/win/src/sharedmem_ipc_client.cc
blob: fa6a8779aa4b0b00d9621e3c939d585a546f58cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <string.h>
#include "sandbox/win/src/sharedmem_ipc_client.h"
#include "sandbox/win/src/sandbox.h"
#include "sandbox/win/src/crosscall_client.h"
#include "sandbox/win/src/crosscall_params.h"
#include "base/logging.h"

namespace sandbox {

// Get the base of the data buffer of the channel; this is where the input
// parameters get serialized. Since they get serialized directly into the
// channel we avoid one copy.
void* SharedMemIPCClient::GetBuffer() {
  bool failure = false;
  size_t ix = LockFreeChannel(&failure);
  if (failure) {
    return NULL;
  }
  return reinterpret_cast<char*>(control_) +
         control_->channels[ix].channel_base;
}

// If we need to cancel an IPC before issuing DoCall
// our client should call FreeBuffer with the same pointer
// returned by GetBuffer.
void SharedMemIPCClient::FreeBuffer(void* buffer) {
  size_t num = ChannelIndexFromBuffer(buffer);
  ChannelControl* channel = control_->channels;
  LONG result = ::InterlockedExchange(&channel[num].state, kFreeChannel);
  DCHECK_NE(kFreeChannel, static_cast<ChannelState>(result));
  result;
}

// The constructor simply casts the shared memory to the internal
// structures. This is a cheap step that is why this IPC object can
// and should be constructed per call.
SharedMemIPCClient::SharedMemIPCClient(void* shared_mem)
    : control_(reinterpret_cast<IPCControl*>(shared_mem)) {
  first_base_ = reinterpret_cast<char*>(shared_mem) +
               control_->channels[0].channel_base;
  // There must be at least one channel.
  DCHECK(0 != control_->channels_count);
}

// Do the IPC. At this point the channel should have already been
// filled with the serialized input parameters.
// We follow the pattern explained in the header file.
ResultCode SharedMemIPCClient::DoCall(CrossCallParams* params,
                                      CrossCallReturn* answer) {
  if (!control_->server_alive)
    return SBOX_ERROR_CHANNEL_ERROR;

  size_t num = ChannelIndexFromBuffer(params->GetBuffer());
  ChannelControl* channel = control_->channels;
  // Note that the IPC tag goes outside the buffer as well inside
  // the buffer. This should enable the server to prioritize based on
  // IPC tags without having to de-serialize the entire message.
  channel[num].ipc_tag = params->GetTag();

  // Wait for the server to service this IPC call. After kIPCWaitTimeOut1
  // we check if the server_alive mutex was abandoned which will indicate
  // that the server has died.

  // While the atomic signaling and waiting is not a requirement, it
  // is nice because we save a trip to kernel.
  DWORD wait = ::SignalObjectAndWait(channel[num].ping_event,
                                     channel[num].pong_event,
                                     kIPCWaitTimeOut1, FALSE);
  if (WAIT_TIMEOUT == wait) {
    // The server is taking too long. Enter a loop were we check if the
    // server_alive mutex has been abandoned which would signal a server crash
    // or else we keep waiting for a response.
    while (true) {
      wait = ::WaitForSingleObject(control_->server_alive, 0);
      if (WAIT_TIMEOUT == wait) {
        // Server seems still alive. We already signaled so here we just wait.
        wait = ::WaitForSingleObject(channel[num].pong_event, kIPCWaitTimeOut1);
        if (WAIT_OBJECT_0 == wait) {
          // The server took a long time but responded.
          break;
        } else if (WAIT_TIMEOUT == wait) {
          continue;
        } else {
          return SBOX_ERROR_CHANNEL_ERROR;
        }
      } else {
        // The server has crashed and windows has signaled the mutex as
        // abandoned.
        ::InterlockedExchange(&channel[num].state, kAbandonedChannel);
        control_->server_alive = 0;
        return SBOX_ERROR_CHANNEL_ERROR;
      }
    }
  } else if (WAIT_OBJECT_0 != wait) {
    // Probably the server crashed before the kIPCWaitTimeOut1 occurred.
    return SBOX_ERROR_CHANNEL_ERROR;
  }

  // The server has returned an answer, copy it and free the channel.
  memcpy(answer, params->GetCallReturn(), sizeof(CrossCallReturn));

  // Return the IPC state It can indicate that while the IPC has
  // completed some error in the Broker has caused to not return valid
  // results.
  return answer->call_outcome;
}

// Locking a channel is a simple as looping over all the channels
// looking for one that is has state = kFreeChannel and atomically
// swapping it to kBusyChannel.
// If there is no free channel, then we must back off so some other
// thread makes progress and frees a channel. To back off we sleep.
size_t SharedMemIPCClient::LockFreeChannel(bool* severe_failure) {
  if (0 == control_->channels_count) {
    *severe_failure = true;
    return 0;
  }
  ChannelControl* channel = control_->channels;
  do {
    for (size_t ix = 0; ix != control_->channels_count; ++ix) {
      if (kFreeChannel == ::InterlockedCompareExchange(&channel[ix].state,
                                                       kBusyChannel,
                                                       kFreeChannel)) {
          *severe_failure = false;
          return ix;
      }
    }
    // We did not find any available channel, maybe the server is dead.
    DWORD wait = ::WaitForSingleObject(control_->server_alive,
                                       kIPCWaitTimeOut2);
    if (WAIT_TIMEOUT != wait) {
      // The server is dead and we outlive it enough to get in trouble.
      *severe_failure = true;
      return 0;
    }
  }
  while (true);
}

// Find out which channel we are from the pointer returned by GetBuffer.
size_t SharedMemIPCClient::ChannelIndexFromBuffer(const void* buffer) {
  ptrdiff_t d = reinterpret_cast<const char*>(buffer) - first_base_;
  size_t num = d/kIPCChannelSize;
  DCHECK_LT(num, control_->channels_count);
  return (num);
}

}  // namespace sandbox