kwin/src/wayland/idle_interface.cpp
Martin Gräßlin d96319b23d [server] Minimum supported idle timeout is 5 sec
A system isn't idle by definition below 5 sec. Before it's not possible
to properly determine whether the user is still interacting with the
system or not. Thus such a short timeout is not sufficient to determine
whether the system is idle.

Furthermore allowing short timeouts like 1 msec can be used to gather
information from the system. It would allow to reconstruct the timestamp
changes on the seat very reliable, which we do not want a Client to know.

And also this can be used to get events on each key press and key release
and the time between each of the events. This can be used to gather
statistics which would in worst case allow to reconstruct what the user
is typing. Determining where a word starts and ends should be relatively
straight forward if you know the timing. With the length of the word and
the statistics of alphabetic distribution it becomes possible to
reconstruct some words. And also individual letters. With enough
statistic and some known words one can determine how long it takes to
press a certain letter. At that point the idle interface would be a
keylogger.

To prevent such attacks the timestamp is now modified to be at least
5 sec. Why 5 sec? Because it's the smallest timestamp used in the
KIdleTime example application which I do not want to break.
5 sec are long enough to destroy this possible attack.

REVIEW: 126220
2015-12-08 14:15:08 +01:00

209 lines
6.5 KiB
C++

/********************************************************************
Copyright 2015 Martin Gräßlin <mgraesslin@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "idle_interface.h"
#include "display.h"
#include "global_p.h"
#include "resource_p.h"
#include "seat_interface.h"
#include <QTimer>
#include <wayland-server.h>
#include <wayland-idle-server-protocol.h>
namespace KWayland
{
namespace Server
{
class IdleInterface::Private : public Global::Private
{
public:
Private(IdleInterface *q, Display *d);
private:
void bind(wl_client *client, uint32_t version, uint32_t id) override;
static void getIdleTimeoutCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *seat, uint32_t timeout);
static void unbind(wl_resource *resource);
static Private *cast(wl_resource *r) {
return reinterpret_cast<Private*>(wl_resource_get_user_data(r));
}
IdleInterface *q;
static const struct org_kde_kwin_idle_interface s_interface;
static const quint32 s_version;
};
class IdleTimeoutInterface::Private : public Resource::Private
{
public:
Private(SeatInterface *seat, IdleTimeoutInterface *q, IdleInterface *manager, wl_resource *parentResource);
~Private();
void setup(quint32 timeout);
SeatInterface *seat;
QTimer *timer = nullptr;
private:
static void releaseCallback(wl_client *client, wl_resource *resource);
static void simulateUserActivityCallback(wl_client *client, wl_resource *resource);
IdleTimeoutInterface *q_func() {
return reinterpret_cast<IdleTimeoutInterface*>(q);
}
static const struct org_kde_kwin_idle_timeout_interface s_interface;
};
const quint32 IdleInterface::Private::s_version = 1;
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct org_kde_kwin_idle_interface IdleInterface::Private::s_interface = {
getIdleTimeoutCallback
};
#endif
IdleInterface::Private::Private(IdleInterface *q, Display *d)
: Global::Private(d, &org_kde_kwin_idle_interface, s_version)
, q(q)
{
}
void IdleInterface::Private::getIdleTimeoutCallback(wl_client *client, wl_resource *resource, uint32_t id, wl_resource *seat, uint32_t timeout)
{
Private *p = cast(resource);
SeatInterface *s = SeatInterface::get(seat);
Q_ASSERT(s);
IdleTimeoutInterface *idleTimeout = new IdleTimeoutInterface(s, p->q, resource);
idleTimeout->create(p->display->getConnection(client), wl_resource_get_version(resource), id);
if (!idleTimeout->resource()) {
wl_resource_post_no_memory(resource);
delete idleTimeout;
return;
}
idleTimeout->d_func()->setup(timeout);
}
void IdleInterface::Private::bind(wl_client *client, uint32_t version, uint32_t id)
{
auto c = display->getConnection(client);
wl_resource *resource = c->createResource(&org_kde_kwin_idle_interface, qMin(version, s_version), id);
if (!resource) {
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(resource, &s_interface, this, unbind);
// TODO: should we track?
}
void IdleInterface::Private::unbind(wl_resource *resource)
{
Q_UNUSED(resource)
}
IdleInterface::IdleInterface(Display *display, QObject *parent)
: Global(new Private(this, display), parent)
{
}
IdleInterface::~IdleInterface() = default;
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct org_kde_kwin_idle_timeout_interface IdleTimeoutInterface::Private::s_interface = {
releaseCallback,
simulateUserActivityCallback
};
#endif
IdleTimeoutInterface::Private::Private(SeatInterface *seat, IdleTimeoutInterface *q, IdleInterface *manager, wl_resource *parentResource)
: Resource::Private(q, manager, parentResource, &org_kde_kwin_idle_timeout_interface, &s_interface)
, seat(seat)
{
}
IdleTimeoutInterface::Private::~Private() = default;
void IdleTimeoutInterface::Private::releaseCallback(wl_client* client, wl_resource* resource)
{
Q_UNUSED(client);
Private *p = reinterpret_cast<Private*>(wl_resource_get_user_data(resource));
wl_resource_destroy(resource);
p->q->deleteLater();
}
void IdleTimeoutInterface::Private::simulateUserActivityCallback(wl_client *client, wl_resource *resource)
{
Q_UNUSED(client);
Private *p = reinterpret_cast<Private*>(wl_resource_get_user_data(resource));
if (!p->timer) {
// not yet configured
return;
}
if (!p->timer->isActive() && p->resource) {
org_kde_kwin_idle_timeout_send_resumed(p->resource);
}
p->timer->start();
}
void IdleTimeoutInterface::Private::setup(quint32 timeout)
{
if (timer) {
return;
}
timer = new QTimer(q);
timer->setSingleShot(true);
// less than 5 sec is not idle by definition
timer->setInterval(qMax(timeout, 5000u));
QObject::connect(timer, &QTimer::timeout, q,
[this] {
if (resource) {
org_kde_kwin_idle_timeout_send_idle(resource);
}
}
);
timer->start();
}
IdleTimeoutInterface::IdleTimeoutInterface(SeatInterface *seat, IdleInterface *parent, wl_resource *parentResource)
: Resource(new Private(seat, this, parent, parentResource))
{
connect(seat, &SeatInterface::timestampChanged, this,
[this] {
Q_D();
if (!d->timer) {
// not yet configured
return;
}
if (!d->timer->isActive() && d->resource) {
org_kde_kwin_idle_timeout_send_resumed(d->resource);
}
d->timer->start();
}
);
}
IdleTimeoutInterface::~IdleTimeoutInterface() = default;
IdleTimeoutInterface::Private *IdleTimeoutInterface::d_func() const
{
return reinterpret_cast<IdleTimeoutInterface::Private*>(d.data());
}
}
}