Merge lp:~robertcarr/mir/implement-client-credentials into lp:~mir-team/mir/trunk
- implement-client-credentials
- Merge into trunk
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 |
Related bugs: |
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
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_
359 + {
360 + std::string error_message = "Client with pid " + request-
361 + error_message += " is not authorized to connect as ";
362 + error_message += request-
363 +
364 + BOOST_THROW_
365 + }
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...
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:777
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:778
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:779
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Daniel van Vugt (vanvugt) wrote : | # |
I have no opinion till further notice...
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.
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_
287 + 0,
288 + ¶meter_message,
289 + &result_message,
290 + callback.get());
This seems like a bad use of an exception but I won't block on it.
Alan Griffiths (alan-griffiths) wrote : | # |
66 + virtual ~SessionAuthori
This is a new interface, make destructor nothrow ("= default;")
~~~~
310 + template<class ResultMessage>
311 + void invoke(
312 + void (protobuf:
313 + ::google:
314 + const ::mir::
315 + ResultMessage* response,
316 + ::google:
317 + mir::protobuf:
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 ProtobufSocketC
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.
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.
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.
Kevin DuBois (kdub) wrote : | # |
> IMO we should refuse the connection earlier (before reading the connect
> message) - in ProtobufSocketC
> 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
Alan Griffiths (alan-griffiths) wrote : | # |
Setting to WIP pending anything changing
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.
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:781
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Alan Griffiths (alan-griffiths) wrote : | # |
My larger concerns are addressed. But a few smaller ones:
350 - std::shared_
351 + std::shared_
...
393 - void on_new_
394 + void on_new_
we don't need non-const access to session.
~~~~
300 + template<class ResultMessage>
301 + void invoke(
302 + void (protobuf:
303 + ::google:
304 + const ::mir::
305 + ResultMessage* response,
306 + ::google:
307 + mir::protobuf:
No need for this to be a template - the exact return type (and even the exact function) is known.
~~~~
362 + session-
363 connected_
364
365 - session-
I don't believe it makes a difference, but do we need this ordering changed?
~~~~
134 +
135 +
Do we need this whitespace?
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.
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).
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:783
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Robert Ancell (robert-ancell) wrote : | # |
Still has changes to the .proto, but they're not required:
+ optional int32 client_pid = 2;
Robert Ancell (robert-ancell) wrote : | # |
In tests/mir_
Otherwise merge looks good to me.
Alan Griffiths (alan-griffiths) wrote : | # |
272 + if (!session_
273 + {
274 + start_accept();
275 + return;
276 + }
277 +
278 connected_
279 -
280 session-
281 }
282 start_accept();
if (session_
{
}
}
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
Alan Griffiths (alan-griffiths) wrote : | # |
I'm seeing a test failure with this branch:
$ bin/unit-tests --gtest_
Note: Google Test filter = ProtobufCommuni
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from ProtobufCommuni
[ RUN ] ProtobufCommuni
/home/alan/
Mock function called more times than expected - taking default action specified at:
/home/alan/
Function call: disconnect_done()
Expected: to be never called
Actual: called once - over-saturated and active
[ FAILED ] ProtobufCommuni
[----------] 1 test from ProtobufCommuni
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (4 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] ProtobufCommuni
1 FAILED TEST
Robert Carr (robertcarr) wrote : | # |
double_
Suggested cleanups applied
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:787
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Robert Ancell (robert-ancell) : | # |
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(
712 - auto len = strlen(
713 -
714 - do
715 - {
716 - std::this_
717 - }
718 - while ((connect(sockfd, (struct sockaddr *)&remote, len) == -1)
719 - && (std::chrono:
720 -
721 - close(sockfd);
722 -
I wish unrelated changes would go in separate MPs - it makes things easier to follow.
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.
Preview Diff
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, ¶meters); |
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>()); |
FAILED: Continuous integration, rev:776 jenkins. qa.ubuntu. com/job/ mir-ci/ 848/ jenkins. qa.ubuntu. com/job/ mir-android- saucy-i386- build/1075/ console jenkins. qa.ubuntu. com/job/ mir-clang- saucy-amd64- build/960/ console jenkins. qa.ubuntu. com/job/ mir-saucy- amd64-ci/ 86/console jenkins. qa.ubuntu. com/job/ mir-vm- ci-build/ ./distribution= quantal, flavor= amd64/502
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ mir-ci/ 848/rebuild
http://