Skip to content

Commit

Permalink
[timed] Limit settings change to sailfish-datetime group. Fixes JB#47634
Browse files Browse the repository at this point in the history
All user accounts must use shared clock settings and it must be possible
to deny secondary accounts from making system time / timezone changes.

Shared settings are stored in /var/lib/timed/shared_settings directory.
The directory is set up so that it is world readable, but writable only
by members of sailfish-datetime group.

When possible, migrate private settings from /home/nemo to the shared
settings directory.

If timed is running as a user that does not have permissions to change
shared settings, deny change requests received over D-Bus without trying
to act on them.

Signed-off-by: Simo Piiroinen <simo.piiroinen@jollamobile.com>
  • Loading branch information
spiiroin committed Nov 21, 2019
1 parent dbac8cb commit 72988f9
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 25 deletions.
5 changes: 4 additions & 1 deletion rpm/timed-qt5.spec
Expand Up @@ -13,7 +13,7 @@ Requires: tzdata-timed
Requires: systemd
Requires: oneshot
Requires: statefs
Requires: sailfish-setup >= 0.1.7
Requires: sailfish-setup >= 0.1.8
%{_oneshot_groupadd_requires_pre}
%{_oneshot_requires_post}
%{_oneshot_groupadd_requires_post}
Expand Down Expand Up @@ -95,6 +95,7 @@ chmod 755 %{buildroot}%{_oneshotdir}/setcaps-%{name}.sh
install -d %{buildroot}/var/lib/timed
touch %{buildroot}/var/lib/timed/localtime
install -d %{buildroot}/var/lib/timed/shared_events
install -d %{buildroot}/var/lib/timed/shared_settings
# Make /etc/localtime a link to /var/lib/timed/localtime to make system time zone follow timed.
install -d %{buildroot}%{_sysconfdir}
ln -sf /var/lib/timed/localtime %{buildroot}%{_sysconfdir}/localtime
Expand Down Expand Up @@ -153,8 +154,10 @@ fi
%{_oneshotdir}/setcaps-%{name}.sh
%dir %attr(0775,-,timed) /var/lib/timed
%dir %attr(02770,root,sailfish-alarms) /var/lib/timed/shared_events
%dir %attr(02775,root,sailfish-datetime) /var/lib/timed/shared_settings
%ghost /var/lib/timed/localtime
%ghost /var/lib/timed/shared_events/events.data
%ghost /var/lib/timed/shared_settings/settings.data

%files tests
%defattr(-,root,root,-)
Expand Down
6 changes: 6 additions & 0 deletions src/server/adaptor.h
Expand Up @@ -109,6 +109,12 @@ public slots:
{
Q_UNUSED(message);
log_notice("DBUS::com.nokia.time.wall_clock_settings(%s) by %s", p.SQC, PEER) ;
if (!timed->permissions_shared_settings(true)) {
/* Settings application should hide relevant controls, so we do not
* normally expect to see requests that would need to be denied. */
log_warning("changing settings denied");
return false;
}
// log_debug("%s", string_std_to_q(p.str()).c_str()) ;
return timed->settings->wall_clock_settings(p) ;
}
Expand Down
1 change: 1 addition & 0 deletions src/server/config.type
Expand Up @@ -18,6 +18,7 @@ config_t =
{ name = "queue_threshold_short", type = $integer, value = 1000 },
{ name = "data_directory", type = $bytes, value = ".timed" },
{ name = "shared_events_directory", type = $bytes, value = "/var/lib/timed/shared_events" },
{ name = "shared_settings_directory", type = $bytes, value = "/var/lib/timed/shared_settings" },
{ name = "settings_file", type = $bytes, value = "settings.data" },
{ name = "events_file", type = $bytes, value = "events.data" },

Expand Down
21 changes: 10 additions & 11 deletions src/server/settings.cpp
Expand Up @@ -133,17 +133,16 @@ source_settings::source_settings(Timed *owner) : QObject(owner)
// new options:
alarms_are_enabled = false ;
default_snooze_value = 300 ;
#define _creat(xxx) src[#xxx] = xxx = new xxx##_t ;
_creat(manual_utc) ;
_creat(nitz_utc) ;
_creat(gps_utc) ;
_creat(ntp_utc) ;
_creat(manual_offset) ;
_creat(nitz_offset) ;
_creat(manual_zone) ;
_creat(cellular_zone) ;
_creat(app_snooze) ;
#undef _creat // spell it without 'e' ;-)

src["manual_utc"] = manual_utc = new manual_utc_t;
src["nitz_utc"] = nitz_utc = new nitz_utc_t;
src["gps_utc"] = gps_utc = new gps_utc_t;
src["ntp_utc"] = ntp_utc = new ntp_utc_t;
src["manual_offset"] = manual_offset = new manual_offset_t;
src["nitz_offset"] = nitz_offset = new nitz_offset_t;
src["manual_zone"] = manual_zone = new manual_zone_t;
src["cellular_zone"] = cellular_zone = new cellular_zone_t;
src["app_snooze"] = app_snooze = new app_snooze_t;
}

source_settings::~source_settings()
Expand Down
1 change: 1 addition & 0 deletions src/server/timed-qt5.rc
@@ -1,4 +1,5 @@
data_directory = ".timed",
shared_events_directory = "/var/lib/timed/shared_events",
shared_settings_directory = "/var/lib/timed/shared_settings",
events_file = "events.data",
settings_file = "settings.data" .
106 changes: 94 additions & 12 deletions src/server/timed.cpp
Expand Up @@ -79,6 +79,14 @@
#include <sstream>
#include <iomanip>

/* Supplementary group required for changing clock settings
*
* User must belong to this group in order to:
* - Modify files used for storing shared clock settings
* - Request settings changes over D-Bus
*/
#define GROUP_SAILFISH_DATETIME "sailfish-datetime"

/* Supplementary group required for accessing shared alarms
*
* User must belong to this group in order to:
Expand Down Expand Up @@ -106,6 +114,7 @@ Timed::Timed(int ac, char **av)
, voland_watcher(nullptr)
, private_event_storage(nullptr)
, private_settings_storage(nullptr)
, shared_settings_storage(nullptr)
, shared_event_storage(nullptr)
, short_save_threshold_timer(nullptr)
, long_save_threshold_timer(nullptr)
Expand All @@ -116,6 +125,8 @@ Timed::Timed(int ac, char **av)
, private_data_directory()
, private_events_path()
, private_settings_path()
, shared_settings_directory()
, shared_settings_path()
, shared_events_directory()
, shared_events_path()
, default_gmt_offset(0)
Expand Down Expand Up @@ -302,6 +313,14 @@ void Timed::init_configuration()
}
shared_events_path = shared_events_directory + QDir::separator() + events_file;

/* Shared settings data directory under /var/lib/timed */
shared_settings_directory = QString::fromStdString(c->get("shared_settings_directory")->str());
if (!QDir(shared_settings_directory).exists()) {
log_critical("shared settings directory '%s' does not exist",
qUtf8Printable(shared_settings_directory));
}
shared_settings_path = shared_settings_directory + QDir::separator() + settings_file;

threshold_period_long = c->get("queue_threshold_long")->value() ;
threshold_period_short = c->get("queue_threshold_short")->value() ;
ping_period = c->get("voland_ping_sleep")->value() ;
Expand Down Expand Up @@ -396,6 +415,17 @@ bool Timed::permissions_private_events() const
QString());
}

bool Timed::permissions_shared_settings(bool write_access) const
{
/* Readable by all, writing requires GROUP_SAILFISH_DATETIME */
QString group(write_access ? GROUP_SAILFISH_DATETIME : "");

return permissions_helper("shared_settings_storage",
shared_settings_directory,
write_access,
group);
}

bool Timed::permissions_private_settings(bool write_access) const
{
return permissions_helper("private_settings_storage",
Expand All @@ -408,22 +438,49 @@ bool Timed::permissions_private_settings(bool write_access) const
// * apply customization defaults, if needed
void Timed::init_read_settings()
{
/* Settings must be loaded even if we have read-only access. */
/* When loading settings:
*
* 1. Try shared data
* - expected to exist after migration
* 2. Try private data
* - expected to exist before migration
* - removed once migration is finished
* 3. Use /dev/null
* - we need parse tree for default value processing
* - any empty file will do, but /dev/null is pretty
* much guaranteed to exist and be readable by all
*/

iodata::record *tree = nullptr;

/* Setup shared data storage */
shared_settings_storage = new iodata::storage;
shared_settings_storage->set_primary_path(shared_settings_path.toStdString());
shared_settings_storage->set_secondary_path(shared_settings_path.toStdString() + ".bak");
shared_settings_storage->set_validator(settings_data_validator(), "settings_t");

/* If possible, read shared settings file */
if (access(qUtf8Printable(shared_settings_path), R_OK) == 0
&& permissions_shared_settings(false)) {
tree = shared_settings_storage->load();
}

/* Setup private data storage */
private_settings_storage = new iodata::storage;
if (permissions_private_settings(false)) {
if (access(qUtf8Printable(private_settings_path), R_OK) == 0
&& permissions_private_settings(false)) {
private_settings_storage->set_primary_path(private_settings_path.toStdString());
private_settings_storage->set_secondary_path(private_settings_path.toStdString() + ".bak");
} else {
/* We are not allowed to read configuration data, but default
* value processing requires that we form a valid parse tree
* -> parsing an empty file is sufficient for our purposes.
*/
/* Private data has been migrated / is otherwise unreadable */
private_settings_storage->set_primary_path("/dev/null");
}
private_settings_storage->set_validator(settings_data_validator(), "settings_t");

iodata::record *tree = private_settings_storage->load();
/* If reading shared settings file failed, read private / dummy data */
if (!tree) {
tree = private_settings_storage->load();
}

log_assert(tree, "loading settings failed") ;

Expand Down Expand Up @@ -616,16 +673,19 @@ void Timed::init_load_events()

void Timed::init_start_event_machine()
{
if (permissions_private_settings(true)) {
if (not private_settings_storage->fix_files(false))
log_critical("can't fix the primary settings file");
/* Initialize shared settings file */
if (permissions_shared_settings(true)) {
if (not shared_settings_storage->fix_files(false))
log_critical("can't fix the primary shared settings file");
}

/* Initialize private events file */
if (permissions_private_events()) {
if (not private_event_storage->fix_files(false))
log_critical("can't fix the primary private event queue file");
}

/* Initialize shared events file */
if (permissions_shared_events()) {
if (not shared_event_storage->fix_files(false))
log_critical("can't fix the primary shared event queue file");
Expand Down Expand Up @@ -718,6 +778,7 @@ void Timed::stop_stuff()
delete shared_event_storage;
log_debug() ;
delete private_settings_storage;
delete shared_settings_storage;
log_debug() ;
delete short_save_threshold_timer ;
log_debug() ;
Expand Down Expand Up @@ -888,9 +949,10 @@ void Timed::save_event_queue()

void Timed::save_settings()
{
if (permissions_private_settings(true)) {
/* Try to save shared settings */
if (permissions_shared_settings(true)) {
iodata::record *tree = settings->save();
int res = private_settings_storage->save(tree);
int res = shared_settings_storage->save(tree);

if (res == 0) // primary file
log_info("wall clock settings written");
Expand All @@ -901,6 +963,26 @@ void Timed::save_settings()

delete tree;
}

/* When we have valid shared data, remove stale private files */
if (access(qUtf8Printable(shared_settings_path), R_OK) == 0) {
if (unlink(qUtf8Printable(private_settings_path)) == 0) {
log_warning("%s: stale configuration file removed",
qUtf8Printable(private_settings_path));
} else if (errno != ENOENT) {
log_error("%s: can't remove file: %m",
qUtf8Printable(private_settings_path));
}

QString backup = private_settings_path + ".bak";
if (unlink(qUtf8Printable(backup)) == 0) {
log_warning("%s: stale configuration file removed",
qUtf8Printable(backup));
} else if (errno != ENOENT) {
log_error("%s: can't remove file: %m",
qUtf8Printable(backup));
}
}
}

void Timed::invoke_signal(const nanotime_t &back)
Expand Down
6 changes: 5 additions & 1 deletion src/server/timed.h
Expand Up @@ -98,7 +98,7 @@ struct Timed : public QCoreApplication
public:
bool is_nitz_supported() { return nitz_supported ; }
const string &default_timezone() { return tz_by_default ; }
const QString get_settings_path() { return private_settings_path; }
const QString get_settings_path() { return shared_settings_path; }
void init_first_boot_hwclock_time_adjustment_check();

private:
Expand Down Expand Up @@ -177,6 +177,7 @@ public Q_SLOTS:
QDBusServiceWatcher *voland_watcher ;
iodata::storage *private_event_storage;
iodata::storage *private_settings_storage;
iodata::storage *shared_settings_storage;
iodata::storage *shared_event_storage;

simple_timer *short_save_threshold_timer;
Expand All @@ -188,6 +189,8 @@ public Q_SLOTS:
QString private_data_directory;
QString private_events_path;
QString private_settings_path;
QString shared_settings_directory;
QString shared_settings_path;
QString shared_events_directory;
QString shared_events_path;
int default_gmt_offset ;
Expand All @@ -201,6 +204,7 @@ public Q_SLOTS:
public:
bool permissions_shared_events() const;
bool permissions_private_events() const;
bool permissions_shared_settings(bool write_access) const;
bool permissions_private_settings(bool write_access) const;
void save_settings() ;
private:
Expand Down
1 change: 1 addition & 0 deletions src/server/timed.rc
@@ -1,4 +1,5 @@
data_directory = ".timed",
shared_events_directory = "/var/lib/timed/shared_events",
shared_settings_directory = "/var/lib/timed/shared_settings",
events_file = "events.data",
settings_file = "settings.data" .

0 comments on commit 72988f9

Please sign in to comment.