diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md
index 9f93e3b836..271d8ab1e3 100644
--- a/docs/TRANSIENT-SETTINGS.md
+++ b/docs/TRANSIENT-SETTINGS.md
@@ -192,6 +192,7 @@ All execution-related settings are available for transient units.
✓ PrivateUsers=
✓ ProtectSystem=
✓ ProtectHome=
+✓ ProtectClock=
✓ MountFlags=
✓ MountAPIVFS=
✓ Personality=
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index c4cada2f27..b7f44c9473 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -405,11 +405,11 @@ CapabilityBoundingSet=~CAP_B CAP_C
RestrictAddressFamilies=, RestrictNamespaces=,
PrivateDevices=, ProtectKernelTunables=,
ProtectKernelModules=, ProtectKernelLogs=,
- MemoryDenyWriteExecute=, RestrictRealtime=,
- RestrictSUIDSGID=, DynamicUser= or LockPersonality=
- are specified. Note that even if this setting is overridden by them, systemctl show shows the
- original value of this setting. Also see No New Privileges
+ ProtectClock=, MemoryDenyWriteExecute=,
+ RestrictRealtime=, RestrictSUIDSGID=, DynamicUser=
+ or LockPersonality= are specified. Note that even if this setting is overridden by them,
+ systemctl show shows the original value of this setting.
+ Also see No New Privileges
Flag.
@@ -1296,6 +1296,21 @@ BindReadOnlyPaths=/var/lib/systemd
+
+ ProtectClock=
+
+ Takes a boolean argument. If set, writes to the hardware clock or system clock will be denied.
+ It is recommended to turn this on for most services that do not need modify the clock. Defaults to off. Enabling
+ this option removes CAP_SYS_TIME and CAP_WAKE_ALARM from the
+ capability bounding set for this unit, installs a system call filter to block calls that can set the
+ clock, and DeviceAllow=char-rtc r is implied. This ensures /dev/rtc0,
+ /dev/rtc1, etc are made read only to the service. See
+ systemd.resource-control5
+ for the details about DeviceAllow=.
+
+
+
+
ProtectKernelTunables=
@@ -1807,7 +1822,7 @@ SystemCallErrorNumber=EPERM
mappings. Specifically these are the options PrivateTmp=,
PrivateDevices=, ProtectSystem=, ProtectHome=,
ProtectKernelTunables=, ProtectControlGroups=,
- ProtectKernelLogs=, ReadOnlyPaths=,
+ ProtectKernelLogs=, ProtectClock=, ReadOnlyPaths=,
InaccessiblePaths= and ReadWritePaths=.
diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c
index 392fadf68f..ee782e5689 100644
--- a/src/analyze/analyze-security.c
+++ b/src/analyze/analyze-security.c
@@ -65,6 +65,7 @@ struct security_info {
bool protect_kernel_modules;
bool protect_kernel_tunables;
bool protect_kernel_logs;
+ bool protect_clock;
char *protect_home;
char *protect_system;
@@ -783,6 +784,16 @@ static const struct security_assessor security_assessor_table[] = {
.assess = assess_bool,
.offset = offsetof(struct security_info, protect_kernel_logs),
},
+ {
+ .id = "ProtectClock=",
+ .description_good = "Service cannot write to the hardware clock or system clock",
+ .description_bad = "Service may write to the hardware clock or system clock",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectClock=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, protect_clock),
+ },
{
.id = "ProtectHome=",
.url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHome=",
@@ -1907,6 +1918,7 @@ static int acquire_security_info(sd_bus *bus, const char *name, struct security_
{ "ProtectKernelModules", "b", NULL, offsetof(struct security_info, protect_kernel_modules) },
{ "ProtectKernelTunables", "b", NULL, offsetof(struct security_info, protect_kernel_tunables) },
{ "ProtectKernelLogs", "b", NULL, offsetof(struct security_info, protect_kernel_logs) },
+ { "ProtectClock", "b", NULL, offsetof(struct security_info, protect_clock) },
{ "ProtectSystem", "s", NULL, offsetof(struct security_info, protect_system) },
{ "RemoveIPC", "b", NULL, offsetof(struct security_info, remove_ipc) },
{ "RestrictAddressFamilies", "(bas)", property_read_restrict_address_families, 0 },
@@ -1984,6 +1996,10 @@ static int acquire_security_info(sd_bus *bus, const char *name, struct security_
if (info->protect_kernel_logs)
info->capability_bounding_set &= ~(UINT64_C(1) << CAP_SYSLOG);
+ if (info->protect_clock)
+ info->capability_bounding_set &= ~((UINT64_C(1) << CAP_SYS_TIME) |
+ (UINT64_C(1) << CAP_WAKE_ALARM));
+
if (info->private_devices)
info->capability_bounding_set &= ~((UINT64_C(1) << CAP_MKNOD) |
(UINT64_C(1) << CAP_SYS_RAWIO));
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index c6772ba843..4f412649c8 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -1284,6 +1284,9 @@ int bus_exec_context_set_transient_property(
if (streq(name, "ProtectKernelLogs"))
return bus_set_transient_bool(u, name, &c->protect_kernel_logs, message, flags, error);
+ if (streq(name, "ProtectClock"))
+ return bus_set_transient_bool(u, name, &c->protect_clock, message, flags, error);
+
if (streq(name, "ProtectControlGroups"))
return bus_set_transient_bool(u, name, &c->protect_control_groups, message, flags, error);
diff --git a/src/core/execute.c b/src/core/execute.c
index f3d2005637..59d7714f2c 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -1402,6 +1402,7 @@ static bool context_has_no_new_privileges(const ExecContext *c) {
c->restrict_realtime ||
c->restrict_suid_sgid ||
exec_context_restrict_namespaces_set(c) ||
+ c->protect_clock ||
c->protect_kernel_tunables ||
c->protect_kernel_modules ||
c->protect_kernel_logs ||
@@ -1564,6 +1565,19 @@ static int apply_protect_kernel_logs(const Unit *u, const ExecContext *c) {
return seccomp_protect_syslog();
}
+static int apply_protect_clock(const Unit *u, const ExecContext *c) {
+ assert(u);
+ assert(c);
+
+ if (!c->protect_clock)
+ return 0;
+
+ if (skip_seccomp_unavailable(u, "ProtectClock="))
+ return 0;
+
+ return seccomp_load_syscall_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_CLOCK, SCMP_ACT_ERRNO(EPERM), false);
+}
+
static int apply_private_devices(const Unit *u, const ExecContext *c) {
assert(u);
assert(c);
@@ -3797,6 +3811,12 @@ static int exec_child(
return log_unit_error_errno(unit, r, "Failed to apply kernel log restrictions: %m");
}
+ r = apply_protect_clock(unit, context);
+ if (r < 0) {
+ *exit_status = EXIT_SECCOMP;
+ return log_unit_error_errno(unit, r, "Failed to apply clock restrictions: %m");
+ }
+
r = apply_private_devices(unit, context);
if (r < 0) {
*exit_status = EXIT_SECCOMP;
@@ -4437,6 +4457,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
"%sProtectKernelTunables: %s\n"
"%sProtectKernelModules: %s\n"
"%sProtectKernelLogs: %s\n"
+ "%sProtectClock: %s\n"
"%sProtectControlGroups: %s\n"
"%sPrivateNetwork: %s\n"
"%sPrivateUsers: %s\n"
@@ -4458,6 +4479,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
prefix, yes_no(c->protect_kernel_tunables),
prefix, yes_no(c->protect_kernel_modules),
prefix, yes_no(c->protect_kernel_logs),
+ prefix, yes_no(c->protect_clock),
prefix, yes_no(c->protect_control_groups),
prefix, yes_no(c->private_network),
prefix, yes_no(c->private_users),
diff --git a/src/core/execute.h b/src/core/execute.h
index c923b1fa21..5aacac4a40 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -258,6 +258,7 @@ struct ExecContext {
bool protect_kernel_tunables;
bool protect_kernel_modules;
bool protect_kernel_logs;
+ bool protect_clock;
bool protect_control_groups;
ProtectSystem protect_system;
ProtectHome protect_home;
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index c1f8ac7bb2..9e622597be 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -116,6 +116,7 @@ $1.PrivateDevices, config_parse_bool, 0,
$1.ProtectKernelTunables, config_parse_bool, 0, offsetof($1, exec_context.protect_kernel_tunables)
$1.ProtectKernelModules, config_parse_bool, 0, offsetof($1, exec_context.protect_kernel_modules)
$1.ProtectKernelLogs, config_parse_bool, 0, offsetof($1, exec_context.protect_kernel_logs)
+$1.ProtectClock, config_parse_bool, 0, offsetof($1, exec_context.protect_clock)
$1.ProtectControlGroups, config_parse_bool, 0, offsetof($1, exec_context.protect_control_groups)
$1.NetworkNamespacePath, config_parse_unit_path_printf, 0, offsetof($1, exec_context.network_namespace_path)
$1.PrivateNetwork, config_parse_bool, 0, offsetof($1, exec_context.private_network)
diff --git a/src/core/unit.c b/src/core/unit.c
index c629a1a9ce..8570eaefb4 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -4287,6 +4287,9 @@ int unit_patch_contexts(Unit *u) {
if (ec->protect_kernel_logs)
ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_SYSLOG);
+ if (ec->protect_clock)
+ ec->capability_bounding_set &= ~((UINT64_C(1) << CAP_SYS_TIME) | (UINT64_C(1) << CAP_WAKE_ALARM));
+
if (ec->dynamic_user) {
if (!ec->user) {
r = user_from_unit_name(u, &ec->user);
@@ -4345,6 +4348,12 @@ int unit_patch_contexts(Unit *u) {
if (r < 0)
return r;
}
+
+ if (ec->protect_clock) {
+ r = cgroup_add_device_allow(cc, "char-rtc", "r");
+ if (r < 0)
+ return r;
+ }
}
return 0;
diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c
index 22a15493d7..254007ef11 100644
--- a/src/shared/bus-unit-util.c
+++ b/src/shared/bus-unit-util.c
@@ -854,6 +854,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
"ProtectKernelTunables",
"ProtectKernelModules",
"ProtectKernelLogs",
+ "ProtectClock",
"ProtectControlGroups",
"MountAPIVFS",
"CPUSchedulingResetOnFork",