Mir

Merge lp:~robertcarr/mir/implement-client-credentials into lp:~mir-team/mir/trunk

Proposed by Robert Carr
Status: Merged
Approved by: Alan Griffiths
Approved revision: no longer in the source branch.
Merged at revision: 852
Proposed branch: lp:~robertcarr/mir/implement-client-credentials
Merge into: lp:~mir-team/mir/trunk
Diff against target: 769 lines (+431/-31)
18 files modified
include/server/mir/default_server_configuration.h (+3/-0)
include/server/mir/frontend/session_authorizer.h (+45/-0)
include/test/mir_test_doubles/stub_session_authorizer.h (+43/-0)
src/client/mir_connection.cpp (+10/-5)
src/client/rpc/mir_socket_rpc_channel.cpp (+7/-0)
src/server/default_server_configuration.cpp (+18/-0)
src/server/frontend/make_protobuf_socket_communicator.cpp (+1/-0)
src/server/frontend/message_processor.h (+4/-0)
src/server/frontend/protobuf_socket_communicator.cpp (+8/-5)
src/server/frontend/protobuf_socket_communicator.h (+4/-1)
src/server/frontend/socket_session.cpp (+16/-0)
src/server/frontend/socket_session.h (+4/-0)
tests/acceptance-tests/CMakeLists.txt (+1/-0)
tests/acceptance-tests/test_client_authorization.cpp (+257/-0)
tests/acceptance-tests/test_client_library.cpp (+2/-1)
tests/mir_test_doubles/test_protobuf_socket_server.cpp (+2/-0)
tests/mir_test_framework/socket_detect_server.cpp (+0/-17)
tests/unit-tests/frontend/test_protobuf_communicator.cpp (+6/-2)
To merge this branch: bzr merge lp:~robertcarr/mir/implement-client-credentials
Reviewer Review Type Date Requested Status
Gerry Boland (community) Approve
Alan Griffiths Approve
Robert Ancell Approve
PS Jenkins bot (community) continuous-integration Approve
Daniel van Vugt Abstain
Review via email: mp+171889@code.launchpad.net

Commit message

Implement a connection authorization mechanism

Description of the change

Implement a connection authorization mechanism for launching applications from the shell. The shell will fork child processes (or later learn the pid through upstart), and wishes to verify that only such processes can connect.

Acceptance test should explain things well.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Robert Ancell (robert-ancell) wrote :

It doesn't make sense that the client PID is in the protocol - the client doesn't volunteer the PID, it is taken from getsockopt ().

430 message ConnectParameters {
431 required string application_name = 1;
432 + optional int32 client_pid = 2;
433 }

Will the whole server go down if one client is not allowed? This shouldn't be an exception right?

358 + if (!session_authorizer->connection_is_allowed(request->client_pid(), request->application_name()))
359 + {
360 + std::string error_message = "Client with pid " + request->client_pid();
361 + error_message += " is not authorized to connect as ";
362 + error_message += request->application_name();
363 +
364 + BOOST_THROW_EXCEPTION(std::runtime_error(error_message));
365 + }

review: Needs Information
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

I think Robert Ancell is right. And assuming that's correct, there's no security or logical benefit to passing the PID in the protocol because it can be spoofed. If you can get the PID from the socket that would be more secure, less redundant and simpler.

Perhaps a security expert would have further suggestions...

review: Needs Fixing
Revision history for this message
Robert Carr (robertcarr) wrote :

Whoops the acceptance test was missed (bzr add).

Of course the pid is retreived from the socket. The interface of mf::SessionMediator however is generated by protoc and it's difficult to modify without encoding messages in the IPC level. So, we use the same pattern we use for FDs, encode them in the protobuf as optional fields, and have the message processor fill them in using out of band data.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Daniel van Vugt (vanvugt) wrote :

I have no opinion till further notice...

review: Abstain
Revision history for this message
Robert Carr (robertcarr) wrote :

>> Will the whole server go down if one client is not allowed? This shouldn't be an exception right?

The exception is caught to be delivered to the client.

Revision history for this message
Robert Ancell (robert-ancell) wrote :

The fd does make sense to be in the .proto because it is set in the message, it just happens to not be encoded with protocol buffers. However the client never sets the pid, it is implicit so it should not be represented in the protocol.

You should pass the PID (and any other required information) in the callback, i.e.:

286 + (display_server.get()->*function)(
287 + 0,
288 + &parameter_message,
289 + &result_message,
290 + callback.get());

This seems like a bad use of an exception but I won't block on it.

review: Needs Fixing
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

66 + virtual ~SessionAuthorizer() {}

This is a new interface, make destructor nothrow ("= default;")

~~~~

310 + template<class ResultMessage>
311 + void invoke(
312 + void (protobuf::DisplayServer::*function)(
313 + ::google::protobuf::RpcController* controller,
314 + const ::mir::protobuf::ConnectParameters* request,
315 + ResultMessage* response,
316 + ::google::protobuf::Closure* done),
317 + mir::protobuf::wire::Invocation const& invocation);

No need for this to be a template - the exact return type (and even the exact function) is known.

~~~~

Somehow this approach seems inelegant: adding a field to the protocol that isn't passed over the wire and then specializing invoke so that we can get the client PID and fill it in on the server side and then handling it in SessionMediator. SessionMediator should really concentrate on translating requests from and to the wire, not on managing connections.

IMO we should refuse the connection earlier (before reading the connect message) - in ProtobufSocketCommunicator::on_new_connection(). That ought to be simpler and avoid throwing exceptions to tear down a connection that we can drop immediately without even registering it with connected_sessions.

review: Needs Fixing
Revision history for this message
Robert Carr (robertcarr) wrote :

The shell wishes to authenticate based on:

(desktop-file, pid), so it's necessary to have read the connect message from the socket containing the session name.

Revision history for this message
Gerry Boland (gerboland) wrote :

Just to clarify what shell wants:
- shell launches an application and takes note of the app's PID. Shell considers app to be in "starting" state.
- when application connects to Mir, want shell to be notified that a client with PID x has connected. Shell then checks if that PID matches any it has launched, and if yes considers that app to be in "running" state

So shell just needs to know the PID of each new clients connecting to Mir.

Right now I don't see why shell would want to reject a client's connection. Have we use-cases for that? I don't think that PID and desktop-file is enough information for it to make such a decision anyway.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

It sounds like we should seek agreement on what the requirement is - this proposal appears to be a facility to reject connections.

Revision history for this message
Kevin DuBois (kdub) wrote :

> IMO we should refuse the connection earlier (before reading the connect
> message) - in ProtobufSocketCommunicator::on_new_connection(). That ought to
> be simpler and avoid throwing exceptions to tear down a connection that we can
> drop immediately without even registering it with connected_sessions.

seems like an appropriate place to reject the connection

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Setting to WIP pending anything changing

Revision history for this message
Gerry Boland (gerboland) wrote :

> It sounds like we should seek agreement on what the requirement is - this
> proposal appears to be a facility to reject connections.

I've since been told shell must have total control, so the ability to reject connections is required.

The approach Robert is taking here will suit us.

Revision history for this message
Robert Carr (robertcarr) wrote :

I've moved the usage of the authorizer to the Communicator which I hope should satisfy everyones concerns.

The client library wasn't prepared to handle errors when sending connect messages, so I've had to make some changes to the socket RPC channel.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

My larger concerns are addressed. But a few smaller ones:

350 - std::shared_ptr<mfd::SocketSession> const& session,
351 + std::shared_ptr<mfd::SocketSession>& session,
...
393 - void on_new_connection(const std::shared_ptr<detail::SocketSession>& session, const boost::system::error_code& ec);
394 + void on_new_connection(std::shared_ptr<detail::SocketSession>& session, const boost::system::error_code& ec);

we don't need non-const access to session.

~~~~

300 + template<class ResultMessage>
301 + void invoke(
302 + void (protobuf::DisplayServer::*function)(
303 + ::google::protobuf::RpcController* controller,
304 + const ::mir::protobuf::ConnectParameters* request,
305 + ResultMessage* response,
306 + ::google::protobuf::Closure* done),
307 + mir::protobuf::wire::Invocation const& invocation);

No need for this to be a template - the exact return type (and even the exact function) is known.

~~~~

362 + session->read_next_message();
363 connected_sessions->add(session);
364
365 - session->read_next_message();

I don't believe it makes a difference, but do we need this ordering changed?

~~~~

134 +
135 +

Do we need this whitespace?

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

This isn't quite what I expected.

Walking through app launch process: shell launches an app, and knows the app's PID. When app connects to Mir, this authorizer will check the app's PID with shell. Shell knows the info of the application with that PID, so can decide to accept or reject.

However after that, how can shell make a connection between the generated Session and the application list it has internally? PID isn't attached to Session, Session only has a "name" property to identify it uniquely. But what sets "name"?

I had expected that shell would set the name that Mir attaches to the Session.

review: Needs Fixing
Revision history for this message
Robert Carr (robertcarr) wrote :

Alan: Issues addressed, mostly leftovers from the first version of the branch (i.e. ::invoke variant is just deleted).

Gerry: I'm having some difficulty figuring out how this session name setting bit should work. So I think in the mean time, we can progress with this, and the limitation that the app only allows one app to be in the 'launching' state at a time (to prevent authentication races).

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Robert Ancell (robert-ancell) wrote :

Still has changes to the .proto, but they're not required:

+ optional int32 client_pid = 2;

Revision history for this message
Robert Ancell (robert-ancell) wrote :

In tests/mir_test_framework/socket_detect_server.cpp you've quoted out some code (which was marked as potentially not required) - this code should be deleted if it's not required right?

Otherwise merge looks good to me.

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

272 + if (!session_authorizer->connection_is_allowed(session->client_pid()))
273 + {
274 + start_accept();
275 + return;
276 + }
277 +
278 connected_sessions->add(session);
279 -
280 session->read_next_message();
281 }
282 start_accept();

        if (session_authorizer->connection_is_allowed(session->client_pid()))
        {
            connected_sessions->add(session);
            session->read_next_message();
        }
    }
    start_accept();

~~~~

368 -
369 +
...
377 -
378 +

Introduces trailing whitespace

~~~~

392 + optional int32 client_pid = 2;

Not used

~~~~

712 - struct sockaddr_un remote;
713 +/* struct sockaddr_un remote;
...
721 - close(sockfd);
722 + close(sockfd);*/

Not suitable for trunk

review: Needs Fixing
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

I'm seeing a test failure with this branch:

$ bin/unit-tests --gtest_filter=ProtobufCommunicator.double_disconnection_attempt_has_no_effect
Note: Google Test filter = ProtobufCommunicator.double_disconnection_attempt_has_no_effect
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ProtobufCommunicator
[ RUN ] ProtobufCommunicator.double_disconnection_attempt_has_no_effect
/home/alan/display_server/trunk-mir/tests/unit-tests/frontend/test_protobuf_communicator.cpp:213: Failure
Mock function called more times than expected - taking default action specified at:
/home/alan/display_server/mir5/tests/mir_test_doubles/test_protobuf_client.cpp:55:
    Function call: disconnect_done()
         Expected: to be never called
           Actual: called once - over-saturated and active
[ FAILED ] ProtobufCommunicator.double_disconnection_attempt_has_no_effect (3 ms)
[----------] 1 test from ProtobufCommunicator (3 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (4 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] ProtobufCommunicator.double_disconnection_attempt_has_no_effect

 1 FAILED TEST

review: Needs Fixing
Revision history for this message
Robert Carr (robertcarr) wrote :

double_disconnect_attempt_has_no_effect updated, the problem is we are now reporting errors on sending messages out of MirSocketRpcChannel, I've changed this to use an exception (previously it was doing nothing, and then in the earlier version of this branch it was creating a synthetic error response), and updated things appropriately.

Suggested cleanups applied

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Robert Ancell (robert-ancell) :
review: Approve
Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

706 - // TODO the code between here and "return" doesn't change error
707 - // TODO does it do anything useful?
708 - struct sockaddr_un remote;
709 - auto sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
710 - remote.sun_family = AF_UNIX;
711 - strcpy(remote.sun_path, socket_file.c_str());
712 - auto len = strlen(remote.sun_path) + sizeof(remote.sun_family);
713 -
714 - do
715 - {
716 - std::this_thread::sleep_for(std::chrono::milliseconds(0));
717 - }
718 - while ((connect(sockfd, (struct sockaddr *)&remote, len) == -1)
719 - && (std::chrono::system_clock::now() < limit));
720 -
721 - close(sockfd);
722 -

I wish unrelated changes would go in separate MPs - it makes things easier to follow.

review: Approve
Revision history for this message
Gerry Boland (gerboland) wrote :

> Gerry: I'm having some difficulty figuring out how this session name setting
> bit should work. So I think in the mean time, we can progress with this, and
> the limitation that the app only allows one app to be in the 'launching' state
> at a time (to prevent authentication races).
Ok, I can work with this for now.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/server/mir/default_server_configuration.h'
2--- include/server/mir/default_server_configuration.h 2013-06-25 13:19:14 +0000
3+++ include/server/mir/default_server_configuration.h 2013-07-10 16:31:29 +0000
4@@ -45,6 +45,7 @@
5 class ProtobufIpcFactory;
6 class SessionMediatorReport;
7 class MessageProcessorReport;
8+class SessionAuthorizer;
9 }
10
11 namespace shell
12@@ -146,6 +147,7 @@
13 * @{ */
14 virtual std::shared_ptr<frontend::SessionMediatorReport> the_session_mediator_report();
15 virtual std::shared_ptr<frontend::MessageProcessorReport> the_message_processor_report();
16+ virtual std::shared_ptr<frontend::SessionAuthorizer> the_session_authorizer();
17 virtual std::shared_ptr<frontend::Shell> the_frontend_shell();
18 /** @} */
19
20@@ -231,6 +233,7 @@
21 CachedPtr<frontend::ProtobufIpcFactory> ipc_factory;
22 CachedPtr<frontend::SessionMediatorReport> session_mediator_report;
23 CachedPtr<frontend::MessageProcessorReport> message_processor_report;
24+ CachedPtr<frontend::SessionAuthorizer> session_authorizer;
25 CachedPtr<compositor::BufferAllocationStrategy> buffer_allocation_strategy;
26 CachedPtr<graphics::Renderer> renderer;
27 CachedPtr<compositor::BufferStreamFactory> buffer_stream_factory;
28
29=== added file 'include/server/mir/frontend/session_authorizer.h'
30--- include/server/mir/frontend/session_authorizer.h 1970-01-01 00:00:00 +0000
31+++ include/server/mir/frontend/session_authorizer.h 2013-07-10 16:31:29 +0000
32@@ -0,0 +1,45 @@
33+/*
34+ * Copyright © 2013 Canonical Ltd.
35+ *
36+ * This program is free software: you can redistribute it and/or modify it
37+ * under the terms of the GNU General Public License version 3,
38+ * as published by the Free Software Foundation.
39+ *
40+ * This program is distributed in the hope that it will be useful,
41+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
42+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
43+ * GNU General Public License for more details.
44+ *
45+ * You should have received a copy of the GNU General Public License
46+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
47+ *
48+ * Authored By: Robert Carr <racarr@canonical.com>
49+ */
50+
51+#ifndef MIR_FRONTEND_SESSION_AUTHORIZER_H_
52+#define MIR_FRONTEND_SESSION_AUTHORIZER_H_
53+
54+#include <sys/types.h>
55+
56+namespace mir
57+{
58+namespace frontend
59+{
60+
61+class SessionAuthorizer
62+{
63+public:
64+ virtual ~SessionAuthorizer() {}
65+
66+ virtual bool connection_is_allowed(pid_t pid) = 0;
67+protected:
68+ SessionAuthorizer() = default;
69+ SessionAuthorizer(SessionAuthorizer const&) = delete;
70+ SessionAuthorizer& operator=(SessionAuthorizer const&) = delete;
71+};
72+
73+}
74+
75+} // namespace mir
76+
77+#endif // MIR_FRONTEND_SESSION_AUTHORIZER_H_
78
79=== added file 'include/test/mir_test_doubles/stub_session_authorizer.h'
80--- include/test/mir_test_doubles/stub_session_authorizer.h 1970-01-01 00:00:00 +0000
81+++ include/test/mir_test_doubles/stub_session_authorizer.h 2013-07-10 16:31:29 +0000
82@@ -0,0 +1,43 @@
83+/*
84+ * Copyright © 2013 Canonical Ltd.
85+ *
86+ * This program is free software: you can redistribute it and/or modify it
87+ * under the terms of the GNU General Public License version 3,
88+ * as published by the Free Software Foundation.
89+ *
90+ * This program is distributed in the hope that it will be useful,
91+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
92+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
93+ * GNU General Public License for more details.
94+ *
95+ * You should have received a copy of the GNU General Public License
96+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
97+ *
98+ * Authored by: Robert Carr <robert.carr@canonical.com>
99+ */
100+
101+#ifndef MIR_TEST_DOUBLES_STUB_SESSION_AUTHORIZER_H_
102+#define MIR_TEST_DOUBLES_STUB_SESSION_AUTHORIZER_H_
103+
104+#include "mir/frontend/session_authorizer.h"
105+
106+namespace mir
107+{
108+namespace test
109+{
110+namespace doubles
111+{
112+
113+class StubSessionAuthorizer : public frontend::SessionAuthorizer
114+{
115+ bool connection_is_allowed(pid_t /* pid */)
116+ {
117+ return true;
118+ }
119+};
120+
121+}
122+}
123+} // namespace mir
124+
125+#endif // MIR_TEST_DOUBLES_STUB_SESSION_AUTHORIZER_H_
126
127=== modified file 'src/client/mir_connection.cpp'
128--- src/client/mir_connection.cpp 2013-07-08 22:22:00 +0000
129+++ src/client/mir_connection.cpp 2013-07-10 16:31:29 +0000
130@@ -195,11 +195,16 @@
131 {
132 std::lock_guard<std::recursive_mutex> lock(mutex);
133
134- server.disconnect(
135- 0,
136- &ignored,
137- &ignored,
138- google::protobuf::NewCallback(this, &MirConnection::done_disconnect));
139+ try
140+ {
141+ server.disconnect(0, &ignored, &ignored,
142+ google::protobuf::NewCallback(this, &MirConnection::done_disconnect));
143+ }
144+ catch (std::exception const& x)
145+ {
146+ set_error_message(std::string("disconnect: ") + x.what());
147+ disconnect_wait_handle.result_received();
148+ }
149
150 return &disconnect_wait_handle;
151 }
152
153=== modified file 'src/client/rpc/mir_socket_rpc_channel.cpp'
154--- src/client/rpc/mir_socket_rpc_channel.cpp 2013-07-08 04:51:27 +0000
155+++ src/client/rpc/mir_socket_rpc_channel.cpp 2013-07-10 16:31:29 +0000
156@@ -27,8 +27,11 @@
157 #include "mir_protobuf_wire.pb.h"
158
159 #include <boost/bind.hpp>
160+#include <boost/throw_exception.hpp>
161+
162 #include <cstring>
163 #include <sstream>
164+#include <stdexcept>
165
166 namespace mclr = mir::client::rpc;
167
168@@ -218,7 +221,11 @@
169 error);
170
171 if (error)
172+ {
173 rpc_report->invocation_failed(invocation, error);
174+
175+ BOOST_THROW_EXCEPTION(std::runtime_error("Failed to send message to server"));
176+ }
177 else
178 rpc_report->invocation_succeeded(invocation);
179 }
180
181=== modified file 'src/server/default_server_configuration.cpp'
182--- src/server/default_server_configuration.cpp 2013-07-03 03:06:24 +0000
183+++ src/server/default_server_configuration.cpp 2013-07-10 16:31:29 +0000
184@@ -33,6 +33,7 @@
185 #include "mir/frontend/session_mediator_report.h"
186 #include "mir/frontend/null_message_processor_report.h"
187 #include "mir/frontend/session_mediator.h"
188+#include "mir/frontend/session_authorizer.h"
189 #include "mir/frontend/resource_cache.h"
190 #include "mir/shell/session_manager.h"
191 #include "mir/shell/registration_order_focus_sequence.h"
192@@ -656,6 +657,23 @@
193 });
194 }
195
196+std::shared_ptr<mf::SessionAuthorizer>
197+mir::DefaultServerConfiguration::the_session_authorizer()
198+{
199+ struct DefaultSessionAuthorizer : public mf::SessionAuthorizer
200+ {
201+ bool connection_is_allowed(pid_t /* pid */)
202+ {
203+ return true;
204+ }
205+ };
206+ return session_authorizer(
207+ [&]()
208+ {
209+ return std::make_shared<DefaultSessionAuthorizer>();
210+ });
211+}
212+
213 std::shared_ptr<mf::SessionMediatorReport>
214 mir::DefaultServerConfiguration::the_session_mediator_report()
215 {
216
217=== modified file 'src/server/frontend/make_protobuf_socket_communicator.cpp'
218--- src/server/frontend/make_protobuf_socket_communicator.cpp 2013-06-19 16:14:40 +0000
219+++ src/server/frontend/make_protobuf_socket_communicator.cpp 2013-07-10 16:31:29 +0000
220@@ -40,6 +40,7 @@
221 return std::make_shared<mf::ProtobufSocketCommunicator>(
222 the_socket_file(),
223 the_ipc_factory(the_frontend_shell(), the_viewable_area(), the_buffer_allocator()),
224+ the_session_authorizer(),
225 threads,
226 [shell_sessions]
227 {
228
229=== modified file 'src/server/frontend/message_processor.h'
230--- src/server/frontend/message_processor.h 2013-05-30 11:20:17 +0000
231+++ src/server/frontend/message_processor.h 2013-07-10 16:31:29 +0000
232@@ -25,6 +25,8 @@
233 #include <iosfwd>
234 #include <cstdint>
235
236+#include <sys/types.h>
237+
238 namespace mir
239 {
240 namespace frontend
241@@ -35,6 +37,8 @@
242 {
243 virtual void send(std::string const& body) = 0;
244 virtual void send_fds(std::vector<int32_t> const& fd) = 0;
245+
246+ virtual pid_t client_pid() = 0;
247 protected:
248 MessageSender() = default;
249 virtual ~MessageSender() { /* TODO: make nothrow */ }
250
251=== modified file 'src/server/frontend/protobuf_socket_communicator.cpp'
252--- src/server/frontend/protobuf_socket_communicator.cpp 2013-06-19 16:14:40 +0000
253+++ src/server/frontend/protobuf_socket_communicator.cpp 2013-07-10 16:31:29 +0000
254@@ -22,20 +22,20 @@
255
256 #include "mir/frontend/communicator_report.h"
257 #include "mir/frontend/protobuf_ipc_factory.h"
258+#include "mir/frontend/session_authorizer.h"
259 #include "mir/protobuf/google_protobuf_guard.h"
260 #include "event_pipe.h"
261
262 #include <boost/signals2.hpp>
263
264-
265 namespace mf = mir::frontend;
266 namespace mfd = mir::frontend::detail;
267 namespace ba = boost::asio;
268
269-
270 mf::ProtobufSocketCommunicator::ProtobufSocketCommunicator(
271 std::string const& socket_file,
272 std::shared_ptr<ProtobufIpcFactory> const& ipc_factory,
273+ std::shared_ptr<mf::SessionAuthorizer> const& session_authorizer,
274 int threads,
275 std::function<void()> const& force_requests_to_complete,
276 std::shared_ptr<CommunicatorReport> const& report)
277@@ -43,6 +43,7 @@
278 acceptor(io_service, socket_file),
279 io_service_threads(threads),
280 ipc_factory(ipc_factory),
281+ session_authorizer(session_authorizer),
282 next_session_id(0),
283 connected_sessions(std::make_shared<mfd::ConnectedSessions<mfd::SocketSession>>()),
284 force_requests_to_complete(force_requests_to_complete),
285@@ -147,9 +148,11 @@
286 {
287 if (!ec)
288 {
289- connected_sessions->add(session);
290-
291- session->read_next_message();
292+ if (session_authorizer->connection_is_allowed(session->client_pid()))
293+ {
294+ connected_sessions->add(session);
295+ session->read_next_message();
296+ }
297 }
298 start_accept();
299 }
300
301=== modified file 'src/server/frontend/protobuf_socket_communicator.h'
302--- src/server/frontend/protobuf_socket_communicator.h 2013-07-07 23:08:41 +0000
303+++ src/server/frontend/protobuf_socket_communicator.h 2013-07-10 16:31:29 +0000
304@@ -45,6 +45,7 @@
305 {
306 class ResourceCache;
307 class ProtobufIpcFactory;
308+class SessionAuthorizer;
309
310 namespace detail
311 {
312@@ -61,6 +62,7 @@
313 explicit ProtobufSocketCommunicator(
314 const std::string& socket_file,
315 std::shared_ptr<ProtobufIpcFactory> const& ipc_factory,
316+ std::shared_ptr<SessionAuthorizer> const& session_authorizer,
317 int threads,
318 std::function<void()> const& force_requests_to_complete,
319 std::shared_ptr<CommunicatorReport> const& report);
320@@ -70,7 +72,7 @@
321
322 private:
323 void start_accept();
324- void on_new_connection(const std::shared_ptr<detail::SocketSession>& session, const boost::system::error_code& ec);
325+ void on_new_connection(std::shared_ptr<detail::SocketSession> const& session, const boost::system::error_code& ec);
326 int next_id();
327
328 const std::string socket_file;
329@@ -78,6 +80,7 @@
330 boost::asio::local::stream_protocol::acceptor acceptor;
331 std::vector<std::thread> io_service_threads;
332 std::shared_ptr<ProtobufIpcFactory> const ipc_factory;
333+ std::shared_ptr<SessionAuthorizer> const session_authorizer;
334 std::atomic<int> next_session_id;
335 std::shared_ptr<detail::ConnectedSessions<detail::SocketSession>> const connected_sessions;
336 std::function<void()> const force_requests_to_complete;
337
338=== modified file 'src/server/frontend/socket_session.cpp'
339--- src/server/frontend/socket_session.cpp 2013-07-09 10:13:52 +0000
340+++ src/server/frontend/socket_session.cpp 2013-07-10 16:31:29 +0000
341@@ -19,6 +19,10 @@
342 #include "socket_session.h"
343
344 #include <boost/signals2.hpp>
345+#include <boost/throw_exception.hpp>
346+
347+#include <stdexcept>
348+
349 #include <sys/types.h>
350 #include <sys/socket.h>
351
352@@ -153,3 +157,15 @@
353 BOOST_THROW_EXCEPTION(std::runtime_error(error.message()));
354 }
355 }
356+
357+pid_t mfd::SocketSession::client_pid()
358+{
359+ struct ucred cr;
360+ socklen_t cl = sizeof(cr);
361+
362+ auto status = getsockopt(socket.native_handle(), SOL_SOCKET, SO_PEERCRED, &cr, &cl);
363+
364+ if (status)
365+ BOOST_THROW_EXCEPTION(std::runtime_error("Failed to query client socket credentials"));
366+ return cr.pid;
367+}
368
369=== modified file 'src/server/frontend/socket_session.h'
370--- src/server/frontend/socket_session.h 2013-06-20 09:50:05 +0000
371+++ src/server/frontend/socket_session.h 2013-07-10 16:31:29 +0000
372@@ -25,6 +25,8 @@
373
374 #include <boost/asio.hpp>
375
376+#include <sys/types.h>
377+
378 namespace mir
379 {
380 namespace frontend
381@@ -55,6 +57,8 @@
382 return socket;
383 }
384
385+ pid_t client_pid();
386+
387 private:
388 void send(std::string const& body);
389 void send_fds(std::vector<int32_t> const& fd);
390
391=== modified file 'tests/acceptance-tests/CMakeLists.txt'
392--- tests/acceptance-tests/CMakeLists.txt 2013-04-29 23:21:58 +0000
393+++ tests/acceptance-tests/CMakeLists.txt 2013-07-10 16:31:29 +0000
394@@ -10,6 +10,7 @@
395 test_test_framework.cpp
396 test_focus_selection.cpp
397 test_server_shutdown.cpp
398+ test_client_authorization.cpp
399 )
400
401 list(APPEND SOURCES
402
403=== added file 'tests/acceptance-tests/test_client_authorization.cpp'
404--- tests/acceptance-tests/test_client_authorization.cpp 1970-01-01 00:00:00 +0000
405+++ tests/acceptance-tests/test_client_authorization.cpp 2013-07-10 16:31:29 +0000
406@@ -0,0 +1,257 @@
407+/*
408+ * Copyright © 2013 Canonical Ltd.
409+ *
410+ * This program is free software: you can redistribute it and/or modify
411+ * it under the terms of the GNU General Public License version 3 as
412+ * published by the Free Software Foundation.
413+ *
414+ * This program is distributed in the hope that it will be useful,
415+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
416+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
417+ * GNU General Public License for more details.
418+ *
419+ * You should have received a copy of the GNU General Public License
420+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
421+ *
422+ * Authored by: Robert Carr <robert.carr@canonical.com>
423+ */
424+
425+#include "mir/frontend/session_authorizer.h"
426+
427+#include <mir_toolkit/mir_client_library.h>
428+
429+#include "mir_test/fake_shared.h"
430+#include "mir_test_framework/display_server_test_fixture.h"
431+
432+#include <gtest/gtest.h>
433+#include <gmock/gmock.h>
434+
435+#include <sys/mman.h>
436+#include <sys/types.h>
437+#include <semaphore.h>
438+#include <time.h>
439+#include <unistd.h>
440+
441+namespace mf = mir::frontend;
442+namespace mt = mir::test;
443+namespace mtf = mir_test_framework;
444+
445+namespace
446+{
447+ char const* const mir_test_socket = mtf::test_socket_file().c_str();
448+}
449+
450+namespace
451+{
452+
453+struct ClientConfigCommon : TestingClientConfiguration
454+{
455+ ClientConfigCommon() :
456+ connection(0),
457+ surface(0)
458+ {
459+ }
460+
461+ static void connection_callback(MirConnection* connection, void* context)
462+ {
463+ ClientConfigCommon* config = reinterpret_cast<ClientConfigCommon *>(context);
464+ config->connection = connection;
465+ }
466+
467+ static void create_surface_callback(MirSurface* surface, void* context)
468+ {
469+ ClientConfigCommon* config = reinterpret_cast<ClientConfigCommon *>(context);
470+ config->surface_created(surface);
471+ }
472+
473+ static void release_surface_callback(MirSurface* surface, void* context)
474+ {
475+ ClientConfigCommon* config = reinterpret_cast<ClientConfigCommon *>(context);
476+ config->surface_released(surface);
477+ }
478+
479+ virtual void connected(MirConnection* new_connection)
480+ {
481+ connection = new_connection;
482+ }
483+
484+ virtual void surface_created(MirSurface* new_surface)
485+ {
486+ surface = new_surface;
487+ }
488+
489+ virtual void surface_released(MirSurface* /*released_surface*/)
490+ {
491+ surface = NULL;
492+ }
493+
494+ MirConnection* connection;
495+ MirSurface* surface;
496+};
497+
498+struct ConnectingClient : ClientConfigCommon
499+{
500+ void exec()
501+ {
502+ mir_wait_for(mir_connect(
503+ mir_test_socket,
504+ __PRETTY_FUNCTION__,
505+ connection_callback,
506+ this));
507+ mir_connection_release(connection);
508+ }
509+};
510+
511+struct ClientPidTestFixture : BespokeDisplayServerTestFixture
512+{
513+ ClientPidTestFixture()
514+ {
515+ shared_region = static_cast<SharedRegion*>(
516+ mmap(NULL, sizeof(SharedRegion), PROT_READ | PROT_WRITE,
517+ MAP_ANONYMOUS | MAP_SHARED, 0, 0));
518+ sem_init(&shared_region->client_pid_set, 1, 0);
519+ shared_region->client_pid = 0;
520+ }
521+
522+ struct SharedRegion
523+ {
524+ sem_t client_pid_set;
525+ pid_t client_pid;
526+
527+ pid_t get_client_process_pid()
528+ {
529+ static time_t const timeout_seconds = 60;
530+ struct timespec abs_timeout = { time(NULL) + timeout_seconds, 0 };
531+ sem_timedwait(&client_pid_set, &abs_timeout);
532+ return client_pid;
533+ }
534+ void post_client_process_pid(pid_t pid)
535+ {
536+ client_pid = pid;
537+ sem_post(&client_pid_set);
538+ }
539+ };
540+
541+ SharedRegion* shared_region;
542+};
543+
544+struct MockSessionAuthorizer : public mf::SessionAuthorizer
545+{
546+ MOCK_METHOD1(connection_is_allowed, bool(pid_t));
547+};
548+
549+}
550+
551+TEST_F(ClientPidTestFixture, session_authorizer_receives_pid_of_connecting_clients)
552+{
553+ struct ServerConfiguration : TestingServerConfiguration
554+ {
555+ ServerConfiguration(ClientPidTestFixture::SharedRegion *shared_region)
556+ : shared_region(shared_region)
557+ {
558+ }
559+
560+ void exec() override
561+ {
562+ using namespace ::testing;
563+ pid_t client_pid = shared_region->get_client_process_pid();
564+
565+ EXPECT_CALL(mock_authorizer, connection_is_allowed(client_pid)).Times(1)
566+ .WillOnce(Return(true));
567+ }
568+
569+ std::shared_ptr<mf::SessionAuthorizer> the_session_authorizer() override
570+ {
571+ return mt::fake_shared(mock_authorizer);
572+ }
573+
574+ ClientPidTestFixture::SharedRegion* shared_region;
575+ MockSessionAuthorizer mock_authorizer;
576+ } server_config(shared_region);
577+ launch_server_process(server_config);
578+
579+
580+ struct ClientConfiguration : ConnectingClient
581+ {
582+ ClientConfiguration(ClientPidTestFixture::SharedRegion *shared_region)
583+ : shared_region(shared_region)
584+ {
585+ }
586+
587+ void exec() override
588+ {
589+ pid_t client_pid = getpid();
590+ shared_region->post_client_process_pid(client_pid);
591+
592+ ConnectingClient::exec();
593+ }
594+
595+ ClientPidTestFixture::SharedRegion* shared_region;
596+ } client_config(shared_region);
597+ launch_client_process(client_config);
598+}
599+
600+TEST_F(ClientPidTestFixture, authorizer_may_prevent_connection_of_clients)
601+{
602+ using namespace ::testing;
603+
604+ struct ServerConfiguration : TestingServerConfiguration
605+ {
606+ ServerConfiguration(ClientPidTestFixture::SharedRegion *shared_region)
607+ : shared_region(shared_region)
608+ {
609+ }
610+
611+ void exec() override
612+ {
613+ using namespace ::testing;
614+ pid_t client_pid = shared_region->get_client_process_pid();
615+
616+ EXPECT_CALL(mock_authorizer, connection_is_allowed(client_pid)).Times(1)
617+ .WillOnce(Return(false));
618+ }
619+
620+ std::shared_ptr<mf::SessionAuthorizer> the_session_authorizer() override
621+ {
622+ return mt::fake_shared(mock_authorizer);
623+ }
624+
625+ ClientPidTestFixture::SharedRegion* shared_region;
626+ MockSessionAuthorizer mock_authorizer;
627+ } server_config(shared_region);
628+ launch_server_process(server_config);
629+
630+
631+ struct ClientConfiguration : ConnectingClient
632+ {
633+ ClientConfiguration(ClientPidTestFixture::SharedRegion *shared_region)
634+ : shared_region(shared_region)
635+ {
636+ }
637+
638+ void exec() override
639+ {
640+ pid_t client_pid = getpid();
641+ shared_region->post_client_process_pid(client_pid);
642+
643+ mir_wait_for(mir_connect(
644+ mir_test_socket,
645+ __PRETTY_FUNCTION__,
646+ connection_callback,
647+ this));
648+ MirSurfaceParameters const parameters =
649+ {
650+ __PRETTY_FUNCTION__,
651+ 1, 1,
652+ mir_pixel_format_abgr_8888,
653+ mir_buffer_usage_hardware
654+ };
655+ mir_connection_create_surface_sync(connection, &parameters);
656+ EXPECT_GT(strlen(mir_connection_get_error_message(connection)), static_cast<unsigned int>(0));
657+ mir_connection_release(connection);
658+ }
659+
660+ ClientPidTestFixture::SharedRegion* shared_region;
661+ } client_config(shared_region);
662+ launch_client_process(client_config);
663+}
664
665=== modified file 'tests/acceptance-tests/test_client_library.cpp'
666--- tests/acceptance-tests/test_client_library.cpp 2013-06-28 10:42:44 +0000
667+++ tests/acceptance-tests/test_client_library.cpp 2013-07-10 16:31:29 +0000
668@@ -736,7 +736,8 @@
669 char const* error = mir_connection_get_error_message(connection);
670
671 if (std::strcmp("connect: No such file or directory", error) &&
672- std::strcmp("Can't find MIR server", error))
673+ std::strcmp("Can't find MIR server", error) &&
674+ std::strcmp("Failed to connect to server socket", error))
675 {
676 FAIL() << error;
677 }
678
679=== modified file 'tests/mir_test_doubles/test_protobuf_socket_server.cpp'
680--- tests/mir_test_doubles/test_protobuf_socket_server.cpp 2013-06-19 16:14:40 +0000
681+++ tests/mir_test_doubles/test_protobuf_socket_server.cpp 2013-07-10 16:31:29 +0000
682@@ -18,6 +18,7 @@
683
684 #include "mir_test/test_protobuf_server.h"
685 #include "mir_test_doubles/stub_ipc_factory.h"
686+#include "mir_test_doubles/stub_session_authorizer.h"
687 #include "mir/frontend/communicator_report.h"
688 #include "src/server/frontend/protobuf_socket_communicator.h"
689
690@@ -35,6 +36,7 @@
691 return std::make_shared<mf::ProtobufSocketCommunicator>(
692 socket_name,
693 factory,
694+ std::make_shared<mtd::StubSessionAuthorizer>(),
695 10,
696 []{},
697 report);
698
699=== modified file 'tests/mir_test_framework/socket_detect_server.cpp'
700--- tests/mir_test_framework/socket_detect_server.cpp 2013-01-11 23:47:40 +0000
701+++ tests/mir_test_framework/socket_detect_server.cpp 2013-07-10 16:31:29 +0000
702@@ -45,23 +45,6 @@
703 }
704 while (error && std::chrono::system_clock::now() < limit);
705
706- // TODO the code between here and "return" doesn't change error
707- // TODO does it do anything useful?
708- struct sockaddr_un remote;
709- auto sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
710- remote.sun_family = AF_UNIX;
711- strcpy(remote.sun_path, socket_file.c_str());
712- auto len = strlen(remote.sun_path) + sizeof(remote.sun_family);
713-
714- do
715- {
716- std::this_thread::sleep_for(std::chrono::milliseconds(0));
717- }
718- while ((connect(sockfd, (struct sockaddr *)&remote, len) == -1)
719- && (std::chrono::system_clock::now() < limit));
720-
721- close(sockfd);
722-
723 return !error;
724 }
725
726
727=== modified file 'tests/unit-tests/frontend/test_protobuf_communicator.cpp'
728--- tests/unit-tests/frontend/test_protobuf_communicator.cpp 2013-06-20 13:23:08 +0000
729+++ tests/unit-tests/frontend/test_protobuf_communicator.cpp 2013-07-10 16:31:29 +0000
730@@ -25,6 +25,7 @@
731 #include "mir_protobuf.pb.h"
732
733 #include "mir_test_doubles/stub_ipc_factory.h"
734+#include "mir_test_doubles/stub_session_authorizer.h"
735 #include "mir_test_doubles/mock_rpc_report.h"
736 #include "mir_test/stub_server_tool.h"
737 #include "mir_test/test_protobuf_client.h"
738@@ -184,7 +185,7 @@
739 }
740
741 TEST_F(ProtobufCommunicator,
742- double_disconnection_attempt_has_no_effect)
743+ double_disconnection_attempt_throws_exception)
744 {
745 using namespace testing;
746
747@@ -211,10 +212,12 @@
748 EXPECT_CALL(*client->rpc_report, invocation_failed(InvocationMethodEq("disconnect"),_));
749 EXPECT_CALL(*client, disconnect_done()).Times(0);
750
751+ EXPECT_THROW({
752 // We don't know if this will be called, so it can't auto destruct
753 std::unique_ptr<google::protobuf::Closure> new_callback(google::protobuf::NewPermanentCallback(client.get(), &mt::TestProtobufClient::disconnect_done));
754 client->display_server.disconnect(0, &client->ignored, &client->ignored, new_callback.get());
755 client->wait_for_disconnect_done();
756+ }, std::runtime_error);
757 }
758
759 TEST_F(ProtobufCommunicator,
760@@ -318,7 +321,8 @@
761 .Times(2);
762
763 auto comms = std::make_shared<mf::ProtobufSocketCommunicator>(
764- "./test_socket1", ipc_factory, 10,
765+ "./test_socket1", ipc_factory,
766+ std::make_shared<mtd::StubSessionAuthorizer>(), 10,
767 std::bind(&MockForceRequests::force_requests_to_complete,
768 &mock_force_requests),
769 std::make_shared<mf::NullCommunicatorReport>());

Subscribers

People subscribed via source and target branches