Mir

Merge lp:~alan-griffiths/mir/fix-1314574 into lp:mir

Proposed by Alan Griffiths
Status: Merged
Approved by: Alan Griffiths
Approved revision: no longer in the source branch.
Merged at revision: 1644
Proposed branch: lp:~alan-griffiths/mir/fix-1314574
Merge into: lp:mir
Prerequisite: lp:~alan-griffiths/mir/basic-test-fixture
Diff against target: 242 lines (+94/-24)
6 files modified
include/server/mir/frontend/session_credentials.h (+0/-3)
src/server/frontend/published_socket_connector.cpp (+6/-0)
src/server/frontend/session_credentials.cpp (+0/-18)
src/server/frontend/socket_messenger.cpp (+54/-3)
src/server/frontend/socket_messenger.h (+6/-0)
tests/acceptance-tests/test_trust_session_helper.cpp (+28/-0)
To merge this branch: bzr merge lp:~alan-griffiths/mir/fix-1314574
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Kevin DuBois (community) Approve
Alexandros Frantzis (community) Approve
Review via email: mp+219814@code.launchpad.net

Commit message

frontend: get the credentials of the real client process when it connects

Description of the change

frontend: get the credentials of the real client process when it connects

Posix mandates that with previously used SO_PEERCRED "The returned credentials are those that were in effect at the time of the call to connect(2) or socketpair(2)."

Note that privileged processes *can* spoof their SCM_CREDENTIALS as I don't think it matters for our use cases. But this seems to be the best we can achieve without relying on non-portable techniques (e.g. apparmor security contexts).

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
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 :

Fetched 19.1 MB in 33s (573 kB/s)
W: Failed to fetch bzip2:/var/lib/apt/lists/partial/ports.ubuntu.com_ubuntu-ports_dists_utopic_universe_source_Sources Hash Sum mismatch

W: Failed to fetch bzip2:/var/lib/apt/lists/partial/ports.ubuntu.com_ubuntu-ports_dists_utopic_universe_binary-armhf_Packages Hash Sum mismatch

Triggering rebuild

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
Alexandros Frantzis (afrantzis) wrote :

153 + auto const ucredp = (struct ucred *) CMSG_DATA(CMSG_FIRSTHDR(&msgh));

C cast.

210 +// Disabled as we can't be sure the mir_demo_client_basic is about

Why depend on mir_demo_client_basic instead of creating a new process and running mir_connect() ourselves? We can also record the actual client PID this way, and compare it directly with what the server got.

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

> 153 + auto const ucredp = (struct ucred *)
> CMSG_DATA(CMSG_FIRSTHDR(&msgh));
>
> C cast.

Done

> 210 +// Disabled as we can't be sure the mir_demo_client_basic is about
>
> Why depend on mir_demo_client_basic instead of creating a new process and
> running mir_connect() ourselves? We can also record the actual client PID this
> way, and compare it directly with what the server got.

I did consider this - but wasn't sure I wanted to get involved with forking a process that already has a mir server running in it (and a socket pair). (Or communicating with one forked earlier after obtaining the socket.) I've been thinking that a small scriptable client might be good for several tests. But including that work in this MP seemed disproportionate.

Revision history for this message
Alexandros Frantzis (afrantzis) wrote :

> I did consider this - but wasn't sure I wanted to get involved with forking a process that already
> has a mir server running in it (and a socket pair). (Or communicating with one forked earlier after
> obtaining the socket.) I've been thinking that a small scriptable client might be good for several
> tests. But including that work in this MP seemed disproportionate.

The scriptable client would be a solution for some cases, but I prefer the flexebility of running completely custom code for clients if possible (especially if some synchronization is needed).

I think that an "exec_in_clean_process(std::function<>, std::vector<int> excluded_fds)" that does a fork followed by an FD cleaning pass (close all open fds, except the supplied list that contains fd numbers for the server socket and pipes used for synchronization (mtf::CrossProcessSync) etc) would be enough.

Anyway, I am happy as long as we have a plan for this for the short(-ish)-term

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

> Anyway, I am happy as long as we have a plan for this for the short(-ish)-term

We have some convergent thoughts, not a plan. Marking with a "TODO" comment

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Kevin DuBois (kdub) wrote :

okay by me

review: Approve
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) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'include/server/mir/frontend/session_credentials.h'
2--- include/server/mir/frontend/session_credentials.h 2014-05-19 02:55:29 +0000
3+++ include/server/mir/frontend/session_credentials.h 2014-05-20 14:06:48 +0000
4@@ -27,8 +27,6 @@
5 class SessionCredentials
6 {
7 public:
8- static SessionCredentials from_socket_fd(int socket_fd);
9-
10 SessionCredentials(pid_t pid, uid_t uid, gid_t gid);
11
12 pid_t pid() const;
13@@ -41,7 +39,6 @@
14 pid_t the_pid;
15 uid_t the_uid;
16 gid_t the_gid;
17-
18 };
19 }
20 }
21
22=== modified file 'src/server/frontend/published_socket_connector.cpp'
23--- src/server/frontend/published_socket_connector.cpp 2014-05-19 02:55:29 +0000
24+++ src/server/frontend/published_socket_connector.cpp 2014-05-20 14:06:48 +0000
25@@ -205,6 +205,12 @@
26 std::function<void(std::shared_ptr<Session> const& session)> const& connect_handler) const
27 {
28 report->creating_session_for(server_socket->native_handle());
29+
30+ /* We set the SO_PASSCRED socket option in order to receive credentials */
31+ auto const optval = 1;
32+ if (setsockopt(server_socket->native_handle(), SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == -1)
33+ BOOST_THROW_EXCEPTION(std::runtime_error("Failed to set SO_PASSCRED"));
34+
35 connection_creator->create_connection_for(server_socket, {connect_handler, this});
36 }
37
38
39=== modified file 'src/server/frontend/session_credentials.cpp'
40--- src/server/frontend/session_credentials.cpp 2014-05-19 02:55:29 +0000
41+++ src/server/frontend/session_credentials.cpp 2014-05-20 14:06:48 +0000
42@@ -17,26 +17,8 @@
43
44 #include "mir/frontend/session_credentials.h"
45
46-#include <sys/socket.h>
47-
48-#include <stdexcept>
49-#include <boost/throw_exception.hpp>
50-
51 namespace mf = mir::frontend;
52
53-mf::SessionCredentials mf::SessionCredentials::from_socket_fd(int socket_fd)
54-{
55- struct ucred cr;
56- socklen_t cl = sizeof(cr);
57-
58- auto status = getsockopt(socket_fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl);
59-
60- if (status)
61- BOOST_THROW_EXCEPTION(std::runtime_error("Failed to query client socket credentials"));
62-
63- return {cr.pid, cr.uid, cr.gid};
64-}
65-
66 mf::SessionCredentials::SessionCredentials(pid_t pid, uid_t uid, gid_t gid) :
67 the_pid{pid},
68 the_uid{uid},
69
70=== modified file 'src/server/frontend/socket_messenger.cpp'
71--- src/server/frontend/socket_messenger.cpp 2014-05-20 08:38:05 +0000
72+++ src/server/frontend/socket_messenger.cpp 2014-05-20 14:06:48 +0000
73@@ -1,5 +1,5 @@
74 /*
75- * Copyright © 2013 Canonical Ltd.
76+ * Copyright © 2013-2014 Canonical Ltd.
77 *
78 * This program is free software: you can redistribute it and/or modify
79 * it under the terms of the GNU General Public License version 3 as
80@@ -21,6 +21,8 @@
81 #include "mir/frontend/session_credentials.h"
82 #include "mir/variable_length_array.h"
83
84+#include <boost/throw_exception.hpp>
85+
86 #include <errno.h>
87 #include <string.h>
88
89@@ -36,9 +38,27 @@
90 {
91 }
92
93+mf::SessionCredentials mfd::SocketMessenger::creator_creds() const
94+{
95+ struct ucred cr;
96+ socklen_t cl = sizeof(cr);
97+
98+ auto status = getsockopt(socket->native_handle(), SOL_SOCKET, SO_PEERCRED, &cr, &cl);
99+
100+ if (status)
101+ BOOST_THROW_EXCEPTION(std::runtime_error("Failed to query client socket credentials"));
102+
103+ return {cr.pid, cr.uid, cr.gid};
104+}
105+
106 mf::SessionCredentials mfd::SocketMessenger::client_creds()
107 {
108- return mf::SessionCredentials::from_socket_fd(socket->native_handle());
109+ if (session_creds.pid() != 0)
110+ return session_creds;
111+
112+ // We've not got the credentials from client yet.
113+ // Return the credentials that created the socket instead.
114+ return creator_creds();
115 }
116
117 void mfd::SocketMessenger::send(char const* data, size_t length, FdSets const& fd_set)
118@@ -92,7 +112,7 @@
119 message->cmsg_len = header.msg_controllen;
120 message->cmsg_level = SOL_SOCKET;
121 message->cmsg_type = SCM_RIGHTS;
122- int *data = (int *)CMSG_DATA(message);
123+ int *data = reinterpret_cast<int*>(CMSG_DATA(message));
124 int i = 0;
125 for (auto &fd: fds)
126 data[i++] = fd;
127@@ -128,7 +148,38 @@
128
129 size_t mfd::SocketMessenger::available_bytes()
130 {
131+ // We call available_bytes() once the client is talking to us
132+ // so this is a pragmatic place to grab the session credentials
133+ if (session_creds.pid() == 0)
134+ update_session_creds();
135+
136 boost::asio::socket_base::bytes_readable command{true};
137 socket->io_control(command);
138 return command.get();
139 }
140+
141+void mfd::SocketMessenger::update_session_creds()
142+{
143+ union {
144+ struct cmsghdr cmh;
145+ char control[CMSG_SPACE(sizeof(ucred))];
146+ } control_un;
147+
148+ control_un.cmh.cmsg_len = CMSG_LEN(sizeof(ucred));
149+ control_un.cmh.cmsg_level = SOL_SOCKET;
150+ control_un.cmh.cmsg_type = SCM_CREDENTIALS;
151+
152+ msghdr msgh;
153+ msgh.msg_name = nullptr;
154+ msgh.msg_namelen = 0;
155+ msgh.msg_iov = nullptr;
156+ msgh.msg_iovlen = 0;
157+ msgh.msg_control = control_un.control;
158+ msgh.msg_controllen = sizeof(control_un.control);
159+
160+ if (recvmsg(socket->native_handle(), &msgh, MSG_PEEK) != -1)
161+ {
162+ auto const ucredp = reinterpret_cast<ucred*>(CMSG_DATA(CMSG_FIRSTHDR(&msgh)));
163+ session_creds = {ucredp->pid, ucredp->uid, ucredp->gid};
164+ }
165+}
166
167=== modified file 'src/server/frontend/socket_messenger.h'
168--- src/server/frontend/socket_messenger.h 2014-05-16 16:46:13 +0000
169+++ src/server/frontend/socket_messenger.h 2014-05-20 14:06:48 +0000
170@@ -20,6 +20,7 @@
171 #define MIR_FRONTEND_SOCKET_MESSENGER_H_
172 #include "message_sender.h"
173 #include "message_receiver.h"
174+#include "mir/frontend/session_credentials.h"
175 #include <mutex>
176
177 namespace mir
178@@ -42,9 +43,14 @@
179 SessionCredentials client_creds() override;
180
181 private:
182+ void update_session_creds();
183+ SessionCredentials creator_creds() const;
184+
185 std::shared_ptr<boost::asio::local::stream_protocol::socket> socket;
186
187 std::mutex message_lock;
188+ SessionCredentials session_creds{0, 0, 0};
189+
190 void send_fds_locked(std::unique_lock<std::mutex> const& lock, std::vector<int32_t> const& fds);
191 };
192 }
193
194=== modified file 'tests/acceptance-tests/test_trust_session_helper.cpp'
195--- tests/acceptance-tests/test_trust_session_helper.cpp 2014-05-19 02:55:29 +0000
196+++ tests/acceptance-tests/test_trust_session_helper.cpp 2014-05-20 14:06:48 +0000
197@@ -27,6 +27,7 @@
198 #include "mir_test_framework/stubbed_server_configuration.h"
199 #include "mir_test_framework/basic_client_server_fixture.h"
200 #include "mir_test_doubles/fake_ipc_factory.h"
201+#include "mir_test/popen.h"
202
203 #include <gtest/gtest.h>
204 #include <gmock/gmock.h>
205@@ -149,6 +150,8 @@
206 sprintf(client_connect_string, "fd://%d", fd);
207 return client_connect_string;
208 }
209+
210+ MOCK_METHOD1(process_line, void(std::string const&));
211 };
212
213 void client_fd_callback(MirConnection*, size_t count, int const* fds, void* context)
214@@ -185,3 +188,28 @@
215
216 mir_connection_release(client_connection);
217 }
218+
219+// TODO we need a nice way to run this (and similar tests that require a separate client process) in CI
220+// Disabled as we can't be sure the mir_demo_client_basic is about
221+TEST_F(TrustSessionHelper, DISABLED_client_pid_is_associated_with_session)
222+{
223+ auto const server_pid = getpid();
224+
225+ mir_connection_new_fds_for_trusted_clients(connection, 1, &client_fd_callback, this);
226+ wait_for_callback(std::chrono::milliseconds(500));
227+
228+ InSequence seq;
229+ EXPECT_CALL(*this, process_line(StrEq("Starting")));
230+ EXPECT_CALL(*this, process_line(StrEq("Connected")));
231+ EXPECT_CALL(*this, process_line(StrEq("Surface created")));
232+ EXPECT_CALL(*this, process_line(StrEq("Surface released")));
233+ EXPECT_CALL(*this, process_line(StrEq("Connection released")));
234+
235+ mir::test::Popen output(std::string("bin/mir_demo_client_basic -m ") + fd_connect_string(actual_fds[0]));
236+
237+ std::string line;
238+ while (output.get_line(line)) process_line(line);
239+
240+ EXPECT_THAT(trusted_helper_mediator->client_pid, Ne(0));
241+ EXPECT_THAT(trusted_helper_mediator->client_pid, Ne(server_pid));
242+}

Subscribers

People subscribed via source and target branches