Merge lp:~jamesh/thumbnailer/dbus-aa-credentials into lp:thumbnailer/devel

Proposed by James Henstridge
Status: Merged
Approved by: Michi Henning
Approved revision: 223
Merged at revision: 214
Proposed branch: lp:~jamesh/thumbnailer/dbus-aa-credentials
Merge into: lp:thumbnailer/devel
Diff against target: 526 lines (+324/-7)
11 files modified
CMakeLists.txt (+1/-0)
debian/control (+1/-0)
src/service/CMakeLists.txt (+8/-2)
src/service/bus.xml (+9/-0)
src/service/credentialscache.cpp (+179/-0)
src/service/credentialscache.h (+81/-0)
src/service/dbusinterface.cpp (+16/-3)
src/service/dbusinterface.h (+4/-0)
src/service/handler.cpp (+21/-1)
src/service/handler.h (+3/-0)
tests/qml/CMakeLists.txt (+1/-1)
To merge this branch: bzr merge lp:~jamesh/thumbnailer/dbus-aa-credentials
Reviewer Review Type Date Requested Status
Michi Henning (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+261824@code.launchpad.net

Commit message

Track the credentials (user ID, AppArmor label) of clients connecting to the D-Bus service. This is not yet used to make security decisions.

Description of the change

This is the first step of the changing our security policy to rely on aa_query_label().

I've introduced a new step for the request handler to determine the AppArmor security context of the client. At the moment we're only printing it out in a log message, but eventually this information will be pushed down to the ThumbnailRequest where it can be used to make the security decision.

You can test this on the desktop by running thumbnailer-service in one terminal and in another run something like:

    aa-exec -p $profile thumbnailer-admin get $filename outdir/

You can get a list of available profiles on the system with "sudo aa-status". When run on the phone, you should see the labels for confined clients.

To avoid excessive GetConnectionCredentials() calls to the bus daemon, we cache the results in the CredentialsCache class.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
221. By James Henstridge

A few style changes.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michi Henning (michihenning) wrote :

Looks really nice, thank you!

Just to show that I read it, I have to make at least one anal-retentive comment ;-)

qWarning() << "CredentialsCache::received_credentials: error retrieving credentials for" << peer << ":" << reply.error().message();

Should be

... CredentialsCache::received_credentials(): error ...

Splitting the line into two shorter ones would make it a little bit more readable.

review: Needs Fixing
222. By James Henstridge

Make qml_test depend on thumbnailer-qml.

223. By James Henstridge

Fix up warning message, from Michi's review.

Revision history for this message
Michi Henning (michihenning) wrote :

Sweet, thank you!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2015-06-06 08:39:23 +0000
3+++ CMakeLists.txt 2015-06-15 01:16:40 +0000
4@@ -97,6 +97,7 @@
5 pkg_check_modules(GIO_DEPS REQUIRED gio-2.0 gio-unix-2.0)
6 pkg_check_modules(IMG_DEPS REQUIRED gdk-pixbuf-2.0 libexif)
7 pkg_check_modules(UNITY_API_DEPS REQUIRED libunity-api)
8+pkg_check_modules(APPARMOR_DEPS REQUIRED libapparmor)
9
10 include_directories(${GST_DEPS_INCLUDE_DIRS})
11 include_directories(${GOBJ_DEPS_INCLUDE_DIRS})
12
13=== modified file 'debian/control'
14--- debian/control 2015-05-05 09:19:37 +0000
15+++ debian/control 2015-06-15 01:16:40 +0000
16@@ -7,6 +7,7 @@
17 cmake-extras,
18 debhelper (>= 9),
19 gstreamer1.0-plugins-good,
20+ libapparmor-dev,
21 libboost-filesystem-dev,
22 libexif-dev,
23 libgdk-pixbuf2.0-dev,
24
25=== modified file 'src/service/CMakeLists.txt'
26--- src/service/CMakeLists.txt 2015-06-08 03:25:50 +0000
27+++ src/service/CMakeLists.txt 2015-06-15 01:16:40 +0000
28@@ -1,10 +1,15 @@
29-add_definitions(${THUMBNAILER_CFLAGS})
30+add_definitions(${APPARMOR_DEPS_CFLAGS})
31
32 qt5_add_dbus_adaptor(adaptor_files dbusinterface.xml dbusinterface.h unity::thumbnailer::service::DBusInterface)
33 qt5_add_dbus_adaptor(adaptor_files admininterface.xml admininterface.h unity::thumbnailer::service::AdminInterface)
34
35+set_source_files_properties(bus.xml PROPERTIES
36+ CLASSNAME BusInterface)
37+qt5_add_dbus_interface(interface_files bus.xml businterface)
38+
39 add_executable(thumbnailer-service
40 admininterface.cpp
41+ credentialscache.cpp
42 dbusinterface.cpp
43 handler.cpp
44 inactivityhandler.cpp
45@@ -12,10 +17,11 @@
46 ratelimiter.cpp
47 stats.cpp
48 ${adaptor_files}
49+ ${interface_files}
50 )
51
52 qt5_use_modules(thumbnailer-service DBus Concurrent)
53-target_link_libraries(thumbnailer-service thumbnailer ${CMAKE_THREAD_LIBS_INIT})
54+target_link_libraries(thumbnailer-service thumbnailer ${CMAKE_THREAD_LIBS_INIT} ${APPARMOR_DEPS_LDFLAGS})
55 set_target_properties(thumbnailer-service PROPERTIES AUTOMOC TRUE)
56 add_dependencies(thumbnailer-service vs-thumb)
57
58
59=== added file 'src/service/bus.xml'
60--- src/service/bus.xml 1970-01-01 00:00:00 +0000
61+++ src/service/bus.xml 2015-06-15 01:16:40 +0000
62@@ -0,0 +1,9 @@
63+<node>
64+ <interface name="org.freedesktop.DBus">
65+ <method name="GetConnectionCredentials">
66+ <arg direction="in" type="s" name="bus_name" />
67+ <arg direction="out" type="a{sv}" name="credentials" />
68+ <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap" />
69+ </method>
70+ </interface>
71+</node>
72
73=== added file 'src/service/credentialscache.cpp'
74--- src/service/credentialscache.cpp 1970-01-01 00:00:00 +0000
75+++ src/service/credentialscache.cpp 2015-06-15 01:16:40 +0000
76@@ -0,0 +1,179 @@
77+/*
78+ * Copyright (C) 2015 Canonical, Ltd.
79+ *
80+ * This library is free software; you can redistribute it and/or modify it under
81+ * the terms of version 3 of the GNU General Public License as published
82+ * by the Free Software Foundation.
83+ *
84+ * This library is distributed in the hope that it will be useful, but WITHOUT
85+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
86+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
87+ * details.
88+ *
89+ * You should have received a copy of the GNU General Public License
90+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
91+ *
92+ * Authors:
93+ * James Henstridge <james.henstridge@canonical.com>
94+ */
95+
96+#include "credentialscache.h"
97+
98+#include <QDBusPendingCallWatcher>
99+
100+#include <assert.h>
101+#include <vector>
102+#include <sys/apparmor.h>
103+
104+using namespace std;
105+
106+namespace {
107+
108+char const DBUS_BUS_NAME[] = "org.freedesktop.DBus";
109+char const DBUS_BUS_PATH[] = "/org/freedesktop/DBus";
110+
111+char const UNIX_USER_ID[] = "UnixUserID";
112+char const LINUX_SECURITY_LABEL[] = "LinuxSecurityLabel";
113+
114+int const MAX_CACHE_SIZE = 50;
115+
116+}
117+
118+namespace unity
119+{
120+
121+namespace thumbnailer
122+{
123+
124+namespace service
125+{
126+
127+struct CredentialsCache::Request
128+{
129+ QDBusPendingCallWatcher watcher;
130+ std::vector<CredentialsCache::Callback> callbacks;
131+
132+ Request(QDBusPendingReply<QVariantMap> call) : watcher(call) {}
133+};
134+
135+CredentialsCache::CredentialsCache(QDBusConnection const& bus)
136+ : bus_daemon_(DBUS_BUS_NAME, DBUS_BUS_PATH, bus)
137+ , apparmor_enabled_(aa_is_enabled())
138+{
139+}
140+
141+CredentialsCache::~CredentialsCache() = default;
142+
143+void CredentialsCache::get(QString const& peer, Callback callback)
144+{
145+ // Return the credentials directly if they are cached
146+ try
147+ {
148+ Credentials const& credentials = cache_.at(peer);
149+ callback(credentials);
150+ return;
151+ }
152+ catch (std::out_of_range const &)
153+ {
154+ // ignore
155+ }
156+
157+ // If the credentials exist in the previous generation of the
158+ // cache, move them to the current generation.
159+ try
160+ {
161+ Credentials& credentials = old_cache_.at(peer);
162+ cache_.emplace(peer, std::move(credentials));
163+ old_cache_.erase(peer);
164+ callback(cache_.at(peer));
165+ return;
166+ }
167+ catch (std::out_of_range const &)
168+ {
169+ // ignore
170+ }
171+
172+ // If the credentials are already being requested, add ourselves
173+ // to the callback list.
174+ try
175+ {
176+ unique_ptr<Request>& request = pending_.at(peer);
177+ request->callbacks.push_back(callback);
178+ return;
179+ }
180+ catch (std::out_of_range const &)
181+ {
182+ // ignore
183+ }
184+
185+ // Ask the bus daemon for the peer's credentials
186+ unique_ptr<Request> request(
187+ new Request(bus_daemon_.GetConnectionCredentials(peer)));
188+ QObject::connect(&request->watcher, &QDBusPendingCallWatcher::finished,
189+ [this, peer](QDBusPendingCallWatcher *watcher)
190+ {
191+ this->received_credentials(peer, *watcher);
192+ });
193+ request->callbacks.push_back(callback);
194+ pending_.emplace(peer, std::move(request));
195+}
196+
197+void CredentialsCache::received_credentials(QString const& peer, QDBusPendingReply<QVariantMap> reply)
198+{
199+ Credentials credentials;
200+ if (reply.isError())
201+ {
202+ qWarning() << "CredentialsCache::received_credentials(): "
203+ "error retrieving credentials for" << peer <<
204+ ":" << reply.error().message();
205+ }
206+ else
207+ {
208+ credentials.valid = true;
209+ // The contents of this map are described in the specification here:
210+ // http://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-get-connection-credentials
211+ credentials.user = reply.value().value(UNIX_USER_ID).value<uint32_t>();
212+ if (apparmor_enabled_)
213+ {
214+ QByteArray label = reply.value().value(LINUX_SECURITY_LABEL).value<QByteArray>();
215+ if (label.size() > 0) {
216+ // The label is null terminated.
217+ assert(label[label.size()-1] == '\0');
218+ label.truncate(label.size() - 1);
219+ // Trim the mode off the end of the label.
220+ int pos = label.lastIndexOf(' ');
221+ if (pos > 0 && label.endsWith(')') && label[pos+1] == '(')
222+ {
223+ label.truncate(pos);
224+ }
225+ credentials.label = string(label.constData(), label.size());
226+ }
227+ }
228+ else
229+ {
230+ // If AppArmor is not enabled, treat peer as unconfined.
231+ credentials.label = "unconfined";
232+ }
233+ }
234+
235+ // If we've hit our maximum cache size, start a new generation.
236+ if (cache_.size() >= MAX_CACHE_SIZE)
237+ {
238+ old_cache_ = std::move(cache_);
239+ cache_.clear();
240+ }
241+ cache_.emplace(peer, credentials);
242+
243+ // Notify anyone waiting on the request and remove it from the map:
244+ for (auto& callback : pending_.at(peer)->callbacks)
245+ {
246+ callback(credentials);
247+ }
248+ pending_.erase(peer);
249+}
250+
251+} // namespace service
252+
253+} // namespace thumbnailer
254+
255+} // namespace unity
256
257=== added file 'src/service/credentialscache.h'
258--- src/service/credentialscache.h 1970-01-01 00:00:00 +0000
259+++ src/service/credentialscache.h 2015-06-15 01:16:40 +0000
260@@ -0,0 +1,81 @@
261+/*
262+ * Copyright (C) 2015 Canonical, Ltd.
263+ *
264+ * This library is free software; you can redistribute it and/or modify it under
265+ * the terms of version 3 of the GNU General Public License as published
266+ * by the Free Software Foundation.
267+ *
268+ * This library is distributed in the hope that it will be useful, but WITHOUT
269+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
270+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
271+ * details.
272+ *
273+ * You should have received a copy of the GNU General Public License
274+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
275+ *
276+ * Authors:
277+ * James Henstridge <james.henstridge@canonical.com>
278+ */
279+
280+#pragma once
281+
282+#include "businterface.h"
283+
284+#include <QDBusConnection>
285+#include <QDBusPendingCall>
286+#include <QString>
287+
288+#include <functional>
289+#include <map>
290+#include <memory>
291+#include <string>
292+#include <sys/types.h>
293+
294+namespace unity
295+{
296+
297+namespace thumbnailer
298+{
299+
300+namespace service
301+{
302+
303+
304+class CredentialsCache final {
305+public:
306+ struct Credentials
307+ {
308+ bool valid = false;
309+ uid_t user = 0;
310+ // Not using QString, because this is not necessarily unicode.
311+ std::string label;
312+ };
313+ typedef std::function<void(Credentials const&)> Callback;
314+
315+ CredentialsCache(QDBusConnection const& bus);
316+ ~CredentialsCache();
317+
318+ CredentialsCache(CredentialsCache const&) = delete;
319+ CredentialsCache& operator=(CredentialsCache const&) = delete;
320+
321+ // Retrieve the security credentials for the given D-Bus peer.
322+ void get(QString const& peer, Callback callback);
323+
324+private:
325+ struct Request;
326+
327+ BusInterface bus_daemon_;
328+ bool apparmor_enabled_;
329+
330+ std::map<QString,Credentials> cache_;
331+ std::map<QString,Credentials> old_cache_;
332+ std::map<QString,std::unique_ptr<Request>> pending_;
333+
334+ void received_credentials(QString const& peer, QDBusPendingReply<QVariantMap> reply);
335+};
336+
337+} // namespace service
338+
339+} // namespace thumbnailer
340+
341+} // namespace unity
342
343=== modified file 'src/service/dbusinterface.cpp'
344--- src/service/dbusinterface.cpp 2015-06-08 03:59:53 +0000
345+++ src/service/dbusinterface.cpp 2015-06-15 01:16:40 +0000
346@@ -52,6 +52,15 @@
347 {
348 }
349
350+CredentialsCache& DBusInterface::credentials()
351+{
352+ if (!credentials_)
353+ {
354+ credentials_.reset(new CredentialsCache(connection()));
355+ }
356+ return *credentials_.get();
357+}
358+
359 QDBusUnixFileDescriptor DBusInterface::GetAlbumArt(QString const& artist,
360 QString const& album,
361 QSize const& requestedSize)
362@@ -64,7 +73,8 @@
363 auto request = thumbnailer_->get_album_art(artist.toStdString(), album.toStdString(), requestedSize);
364 queueRequest(new Handler(connection(), message(),
365 check_thread_pool_, create_thread_pool_,
366- download_limiter_, std::move(request), details));
367+ download_limiter_, credentials(),
368+ std::move(request), details));
369 }
370 // LCOV_EXCL_START
371 catch (exception const& e)
372@@ -89,7 +99,8 @@
373 auto request = thumbnailer_->get_artist_art(artist.toStdString(), album.toStdString(), requestedSize);
374 queueRequest(new Handler(connection(), message(),
375 check_thread_pool_, create_thread_pool_,
376- download_limiter_, std::move(request), details));
377+ download_limiter_, credentials(),
378+ std::move(request), details));
379 }
380 // LCOV_EXCL_START
381 catch (exception const& e)
382@@ -107,6 +118,7 @@
383 QSize const& requestedSize)
384 {
385 std::unique_ptr<ThumbnailRequest> request;
386+
387 try
388 {
389 QString details;
390@@ -115,7 +127,8 @@
391 auto request = thumbnailer_->get_thumbnail(filename.toStdString(), filename_fd.fileDescriptor(), requestedSize);
392 queueRequest(new Handler(connection(), message(),
393 check_thread_pool_, create_thread_pool_,
394- extraction_limiter_, std::move(request), details));
395+ extraction_limiter_, credentials(),
396+ std::move(request), details));
397 }
398 catch (exception const& e)
399 {
400
401=== modified file 'src/service/dbusinterface.h'
402--- src/service/dbusinterface.h 2015-06-05 08:27:20 +0000
403+++ src/service/dbusinterface.h 2015-06-15 01:16:40 +0000
404@@ -19,6 +19,7 @@
405
406 #pragma once
407
408+#include "credentialscache.h"
409 #include "handler.h"
410 #include "ratelimiter.h"
411
412@@ -65,9 +66,12 @@
413 void startInactivity();
414
415 private:
416+ CredentialsCache& credentials();
417+
418 std::shared_ptr<unity::thumbnailer::internal::Thumbnailer> const& thumbnailer_;
419 std::shared_ptr<QThreadPool> check_thread_pool_;
420 std::shared_ptr<QThreadPool> create_thread_pool_;
421+ std::unique_ptr<CredentialsCache> credentials_;
422 std::map<Handler*, std::unique_ptr<Handler>> requests_;
423 std::map<std::string, std::vector<Handler*>> request_keys_;
424 unity::thumbnailer::internal::Settings settings_;
425
426=== modified file 'src/service/handler.cpp'
427--- src/service/handler.cpp 2015-06-08 03:25:50 +0000
428+++ src/service/handler.cpp 2015-06-15 01:16:40 +0000
429@@ -122,6 +122,7 @@
430 shared_ptr<QThreadPool> check_pool;
431 shared_ptr<QThreadPool> create_pool;
432 RateLimiter& limiter;
433+ CredentialsCache& creds;
434 unique_ptr<ThumbnailRequest> request;
435 chrono::system_clock::time_point start_time; // Overall start time
436 chrono::system_clock::time_point finish_time; // Overall finish time
437@@ -139,6 +140,7 @@
438 shared_ptr<QThreadPool> check_pool,
439 shared_ptr<QThreadPool> create_pool,
440 RateLimiter& limiter,
441+ CredentialsCache& creds,
442 unique_ptr<ThumbnailRequest>&& request,
443 QString const& details)
444 : bus(bus)
445@@ -146,6 +148,7 @@
446 , check_pool(check_pool)
447 , create_pool(create_pool)
448 , limiter(limiter)
449+ , creds(creds)
450 , request(move(request))
451 , details(details)
452 {
453@@ -158,9 +161,10 @@
454 shared_ptr<QThreadPool> check_pool,
455 shared_ptr<QThreadPool> create_pool,
456 RateLimiter& limiter,
457+ CredentialsCache& creds,
458 unique_ptr<ThumbnailRequest>&& request,
459 QString const& details)
460- : p(new HandlerPrivate(bus, message, check_pool, create_pool, limiter, move(request), details))
461+ : p(new HandlerPrivate(bus, message, check_pool, create_pool, limiter, creds, move(request), details))
462 {
463 connect(&p->checkWatcher, &QFutureWatcher<FdOrError>::finished, this, &Handler::checkFinished);
464 connect(p->request.get(), &ThumbnailRequest::downloadFinished, this, &Handler::downloadFinished);
465@@ -182,6 +186,22 @@
466
467 void Handler::begin()
468 {
469+ p->creds.get(p->message.service(),
470+ [this](CredentialsCache::Credentials const& credentials)
471+ {
472+ gotCredentials(credentials);
473+ });
474+}
475+
476+void Handler::gotCredentials(CredentialsCache::Credentials const& credentials)
477+{
478+ if (!credentials.valid)
479+ {
480+ sendError("gotCredentials(): " + details() + ": could not retrieve peer credentials");
481+ return;
482+ }
483+ qDebug() << "Peer" << p->message.service() << "has uid =" << credentials.user << "label =" << QString::fromStdString(credentials.label);
484+
485 auto do_check = [this]() -> FdOrError
486 {
487 try
488
489=== modified file 'src/service/handler.h'
490--- src/service/handler.h 2015-06-08 02:34:13 +0000
491+++ src/service/handler.h 2015-06-15 01:16:40 +0000
492@@ -19,6 +19,7 @@
493
494 #pragma once
495
496+#include "credentialscache.h"
497 #include "ratelimiter.h"
498 #include <internal/thumbnailer.h>
499
500@@ -51,6 +52,7 @@
501 std::shared_ptr<QThreadPool> check_pool,
502 std::shared_ptr<QThreadPool> create_pool,
503 RateLimiter& limiter,
504+ CredentialsCache& creds,
505 std::unique_ptr<internal::ThumbnailRequest>&& request,
506 QString const& details);
507 ~Handler();
508@@ -78,6 +80,7 @@
509 private:
510 void sendThumbnail(QDBusUnixFileDescriptor const& unix_fd);
511 void sendError(QString const& error);
512+ void gotCredentials(CredentialsCache::Credentials const& credentials);
513 QDBusUnixFileDescriptor check();
514 QDBusUnixFileDescriptor create();
515
516
517=== modified file 'tests/qml/CMakeLists.txt'
518--- tests/qml/CMakeLists.txt 2015-06-04 08:45:20 +0000
519+++ tests/qml/CMakeLists.txt 2015-06-15 01:16:40 +0000
520@@ -1,5 +1,5 @@
521 add_executable(qml_test qml_test.cpp)
522 qt5_use_modules(qml_test Qml DBus QuickTest)
523 target_link_libraries(qml_test testutils)
524-add_dependencies(qml_test thumbnailer-service)
525+add_dependencies(qml_test thumbnailer-service thumbnailer-qml)
526 add_test(qml xvfb-run -a -s "-screen 0 800x600x24" ./qml_test -import ${CMAKE_BINARY_DIR}/plugins)

Subscribers

People subscribed via source and target branches

to all changes: