2018-08-24 12:01:58

by Ondrej Mosnacek

[permalink] [raw]
Subject: [PATCH ghak10 v5 0/2] audit: Log modifying adjtimex(2) calls

This patchset implements more detailed auditing of the adjtimex(2)
syscall in order to make it possible to:
a) distinguish modifying vs. read-only calls in the audit log
b) reconstruct from the audit log what changes were made and how they
have influenced the system clock

The main motivation is to be able to detect an adversary that tries to
confuse the audit timestamps by changing system time via adjtimex(2),
but at the same time avoid flooding the audit log with records of benign
read-only adjtimex(2) calls.

The current version of the patchset logs the following changes:
- direct injection of timekeeping offset
- adjustment of timekeeping's TAI offset
- NTP value adjustments:
- time_offset
- time_freq
- time_status
- time_adjust
- tick_usec

Changes to the following NTP values are not logged, as they are not
important for security:
- time_maxerror
- time_esterror
- time_constant

Audit kernel GitHub issue: https://github.com/linux-audit/audit-kernel/issues/10

Changes in v5:
- Dropped logging of some less important changes and update commit messages
- No longer mark the patchset as RFC

v4: https://www.redhat.com/archives/linux-audit/2018-August/msg00023.html
Changes in v4:
- Squashed first two patches into one
- Renamed ADJNTPVAL's "type" field to "op" to align with audit record
conventions
- Minor commit message editing
- Cc timekeeping/NTP people for feedback

v3: https://www.redhat.com/archives/linux-audit/2018-July/msg00001.html
Changes in v3:
- Switched to separate records for each variable
- Both old and new value is now reported for each change
- Injecting offset is reported via a separate record (since this
offset consists of two values and is added directly to the clock,
i.e. it doesn't make sense to log old and new value)
- Added example records produced by chronyd -q (see the commit message
of the last patch)

v2: https://www.redhat.com/archives/linux-audit/2018-June/msg00114.html
Changes in v2:
- The audit_adjtime() function has been modified to only log those
fields that contain values that are actually used, resulting in more
compact records.
- The audit_adjtime() call has been moved to do_adjtimex() in
timekeeping.c
- Added an additional patch (for review) that simplifies the detection
if the syscall is read-only.

v1: https://www.redhat.com/archives/linux-audit/2018-June/msg00095.html

Ondrej Mosnacek (2):
audit: Add functions to log time adjustments
timekeeping/ntp: Audit clock/NTP params adjustments

include/linux/audit.h | 21 +++++++++++++++++++++
include/uapi/linux/audit.h | 2 ++
kernel/auditsc.c | 15 +++++++++++++++
kernel/time/ntp.c | 38 ++++++++++++++++++++++++++++++--------
kernel/time/timekeeping.c | 3 +++
5 files changed, 71 insertions(+), 8 deletions(-)

--
2.17.1



2018-08-24 12:01:54

by Ondrej Mosnacek

[permalink] [raw]
Subject: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

This patch adds two auxiliary record types that will be used to annotate
the adjtimex SYSCALL records with the NTP/timekeeping values that have
been changed.

Next, it adds two functions to the audit interface:
- audit_tk_injoffset(), which will be called whenever a timekeeping
offset is injected by a syscall from userspace,
- audit_ntp_adjust(), which will be called whenever an NTP internal
variable is changed by a syscall from userspace.

Quick reference for the fields of the new records:
AUDIT_TIME_INJOFFSET
sec - the 'seconds' part of the offset
nsec - the 'nanoseconds' part of the offset
AUDIT_TIME_ADJNTPVAL
op - which value was adjusted:
offset - corresponding to the time_offset variable
freq - corresponding to the time_freq variable
status - corresponding to the time_status variable
adjust - corresponding to the time_adjust variable
tick - corresponding to the tick_usec variable
tai - corresponding to the timekeeping's TAI offset
old - the old value
new - the new value

Signed-off-by: Ondrej Mosnacek <[email protected]>
---
include/linux/audit.h | 21 +++++++++++++++++++++
include/uapi/linux/audit.h | 2 ++
kernel/auditsc.c | 15 +++++++++++++++
3 files changed, 38 insertions(+)

diff --git a/include/linux/audit.h b/include/linux/audit.h
index 9334fbef7bae..0d084d4b4042 100644
--- a/include/linux/audit.h
+++ b/include/linux/audit.h
@@ -26,6 +26,7 @@
#include <linux/sched.h>
#include <linux/ptrace.h>
#include <uapi/linux/audit.h>
+#include <uapi/linux/timex.h>

#define AUDIT_INO_UNSET ((unsigned long)-1)
#define AUDIT_DEV_UNSET ((dev_t)-1)
@@ -356,6 +357,8 @@ extern void __audit_log_capset(const struct cred *new, const struct cred *old);
extern void __audit_mmap_fd(int fd, int flags);
extern void __audit_log_kern_module(char *name);
extern void __audit_fanotify(unsigned int response);
+extern void __audit_tk_injoffset(struct timespec64 offset);
+extern void __audit_ntp_adjust(const char *type, s64 oldval, s64 newval);

static inline void audit_ipc_obj(struct kern_ipc_perm *ipcp)
{
@@ -458,6 +461,18 @@ static inline void audit_fanotify(unsigned int response)
__audit_fanotify(response);
}

+static inline void audit_tk_injoffset(struct timespec64 offset)
+{
+ if (!audit_dummy_context())
+ __audit_tk_injoffset(offset);
+}
+
+static inline void audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
+{
+ if (!audit_dummy_context())
+ __audit_ntp_adjust(type, oldval, newval);
+}
+
extern int audit_n_rules;
extern int audit_signals;
#else /* CONFIG_AUDITSYSCALL */
@@ -584,6 +599,12 @@ static inline void audit_log_kern_module(char *name)
static inline void audit_fanotify(unsigned int response)
{ }

+static inline void audit_tk_injoffset(struct timespec64 offset)
+{ }
+
+static inline void audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
+{ }
+
static inline void audit_ptrace(struct task_struct *t)
{ }
#define audit_n_rules 0
diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
index 4e3eaba84175..242ce562b41a 100644
--- a/include/uapi/linux/audit.h
+++ b/include/uapi/linux/audit.h
@@ -114,6 +114,8 @@
#define AUDIT_REPLACE 1329 /* Replace auditd if this packet unanswerd */
#define AUDIT_KERN_MODULE 1330 /* Kernel Module events */
#define AUDIT_FANOTIFY 1331 /* Fanotify access decision */
+#define AUDIT_TIME_INJOFFSET 1332 /* Timekeeping offset injected */
+#define AUDIT_TIME_ADJNTPVAL 1333 /* NTP value adjustment */

#define AUDIT_AVC 1400 /* SE Linux avc denial or grant */
#define AUDIT_SELINUX_ERR 1401 /* Internal SE Linux Errors */
diff --git a/kernel/auditsc.c b/kernel/auditsc.c
index fb207466e99b..d355d32d9765 100644
--- a/kernel/auditsc.c
+++ b/kernel/auditsc.c
@@ -2422,6 +2422,21 @@ void __audit_fanotify(unsigned int response)
AUDIT_FANOTIFY, "resp=%u", response);
}

+/* We need to allocate with GFP_ATOMIC here, since these two functions will be
+ * called while holding the timekeeping lock: */
+void __audit_tk_injoffset(struct timespec64 offset)
+{
+ audit_log(audit_context(), GFP_ATOMIC, AUDIT_TIME_INJOFFSET,
+ "sec=%lli nsec=%li", (long long)offset.tv_sec, offset.tv_nsec);
+}
+
+void __audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
+{
+ audit_log(audit_context(), GFP_ATOMIC, AUDIT_TIME_ADJNTPVAL,
+ "op=%s old=%lli new=%lli", type,
+ (long long)oldval, (long long)newval);
+}
+
static void audit_log_task(struct audit_buffer *ab)
{
kuid_t auid, uid;
--
2.17.1


2018-08-24 12:01:54

by Ondrej Mosnacek

[permalink] [raw]
Subject: [PATCH ghak10 v5 2/2] timekeeping/ntp: Audit clock/NTP params adjustments

This patch adds logging of all attempts to either inject an offset into
the clock (producing an AUDIT_TIME_INJOFFSET record) or adjust an NTP
parameter (producing an AUDIT_TIME_ADJNTPVAL record).

For reference, running the following commands:

auditctl -D
auditctl -a exit,always -F arch=b64 -S adjtimex
chronyd -q

produces audit records like this:

type=TIME_ADJNTPVAL msg=audit(1530616044.507:5): op=adjust old=0 new=0
type=SYSCALL msg=audit(1530616044.507:5): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=0 a2=4 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
type=PROCTITLE msg=audit(1530616044.507:5): proctitle=6368726F6E7964002D71
type=SYSCALL msg=audit(1530616044.507:6): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=1 a2=1 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
type=PROCTITLE msg=audit(1530616044.507:6): proctitle=6368726F6E7964002D71
type=TIME_INJOFFSET msg=audit(1530616044.507:7): sec=0 nsec=0
type=TIME_ADJNTPVAL msg=audit(1530616044.507:7): op=status old=64 new=8256
type=SYSCALL msg=audit(1530616044.507:7): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=1 a2=1 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
type=PROCTITLE msg=audit(1530616044.507:7): proctitle=6368726F6E7964002D71
type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=status old=8256 new=8257
type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=offset old=0 new=0
type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=freq old=0 new=0
type=SYSCALL msg=audit(1530616044.507:8): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ab0 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
type=PROCTITLE msg=audit(1530616044.507:8): proctitle=6368726F6E7964002D71
type=TIME_ADJNTPVAL msg=audit(1530616044.507:9): op=status old=8257 new=64
type=SYSCALL msg=audit(1530616044.507:9): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ab0 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
type=PROCTITLE msg=audit(1530616044.507:9): proctitle=6368726F6E7964002D71
type=SYSCALL msg=audit(1530616044.507:10): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78a70 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
type=PROCTITLE msg=audit(1530616044.507:10): proctitle=6368726F6E7964002D71
type=TIME_ADJNTPVAL msg=audit(1530616044.511:11): op=freq old=0 new=49180377088000
type=TIME_ADJNTPVAL msg=audit(1530616044.511:11): op=tick old=10000 new=10000
type=SYSCALL msg=audit(1530616044.511:11): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ad0 a1=0 a2=2710 a3=f42f82a800000 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
type=PROCTITLE msg=audit(1530616044.511:11): proctitle=6368726F6E7964002D71
type=TIME_ADJNTPVAL msg=audit(1530616044.521:12): op=status old=64 new=64
type=SYSCALL msg=audit(1530616044.521:12): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78b40 a1=1 a2=40 a3=f91f6ef84fbab items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
type=PROCTITLE msg=audit(1530616044.521:12): proctitle=6368726F6E7964002D71
type=TIME_INJOFFSET msg=audit(1530616049.652:13): sec=-16 nsec=124887145
type=TIME_ADJNTPVAL msg=audit(1530616049.652:13): op=status old=64 new=8256
type=SYSCALL msg=audit(1530616049.652:13): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78270 a1=1 a2=fffffffffffffff0 a3=137b828205ca12 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
type=PROCTITLE msg=audit(1530616049.652:13): proctitle=6368726F6E7964002D71
type=TIME_ADJNTPVAL msg=audit(1530616033.783:14): op=freq old=49180377088000 new=49180377088000
type=TIME_ADJNTPVAL msg=audit(1530616033.783:14): op=tick old=10000 new=10000
type=SYSCALL msg=audit(1530616033.783:14): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78bc0 a1=0 a2=2710 a3=0 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
type=PROCTITLE msg=audit(1530616033.783:14): proctitle=6368726F6E7964002D71

The chronyd command that produced the above records executed the
following adjtimex(2) syscalls (as per strace output):

adjtimex({modes=ADJ_OFFSET|0x8000, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507215}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
adjtimex({modes=ADJ_MAXERROR, offset=0, freq=0, maxerror=0, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507438}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
adjtimex({modes=ADJ_SETOFFSET|ADJ_NANO, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507604737}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
adjtimex({modes=ADJ_OFFSET|ADJ_STATUS, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_PLL|STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507698330}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
adjtimex({modes=ADJ_STATUS, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507792}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
adjtimex({modes=0, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=508000}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
adjtimex({modes=ADJ_FREQUENCY|ADJ_TICK, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=512146}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
adjtimex({modes=ADJ_MAXERROR|ADJ_ESTERROR|ADJ_STATUS, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=522506}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
adjtimex({modes=ADJ_SETOFFSET|ADJ_NANO, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616033, tv_usec=778717675}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
adjtimex({modes=ADJ_FREQUENCY|ADJ_TICK, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616033, tv_usec=784644657}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)

(The struct timex fields above are from *after* the syscall was
executed, so they contain the current (new) values as set from the
kernel, except of the 'modes' field, which contains the original value
sent by the caller.)

The changes to the time_maxerror, time_esterror, and time_constant
variables are not logged, as these are not important for security.

Note that the records are emitted even when the actual value does not
change (i.e. when there is an explicit attempt to change a value, but
the new value equals the old one).

An overview of changes that can be done via adjtimex(2) (based on
information from Miroslav Lichvar) and whether they are audited:
timekeeping_inject_offset() -- injects offset directly into system
time (AUDITED)
__timekeeping_set_tai_offset() -- sets the offset from the
International Atomic Time
(AUDITED)
NTP variables:
time_offset -- can adjust the clock by up to 0.5 seconds per call
and also speed it up or slow down by up to about
0.05% (43 seconds per day) (AUDITED)
time_freq -- can speed up or slow down by up to about 0.05%
time_status -- can insert/delete leap seconds and it also enables/
disables synchronization of the hardware real-time
clock (AUDITED)
time_maxerror, time_esterror -- change error estimates used to
inform userspace applications
(NOT AUDITED)
time_constant -- controls the speed of the clock adjustments that
are made when time_offset is set (NOT AUDITED)
time_adjust -- can temporarily speed up or slow down the clock by up
to 0.05% (AUDITED)
tick_usec -- a more extreme version of time_freq; can speed up or
slow down the clock by up to 10% (AUDITED)

Cc: Miroslav Lichvar <[email protected]>
Signed-off-by: Ondrej Mosnacek <[email protected]>
---
kernel/time/ntp.c | 38 ++++++++++++++++++++++++++++++--------
kernel/time/timekeeping.c | 3 +++
2 files changed, 33 insertions(+), 8 deletions(-)

diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
index a09ded765f6c..f96c6d326aae 100644
--- a/kernel/time/ntp.c
+++ b/kernel/time/ntp.c
@@ -18,6 +18,7 @@
#include <linux/module.h>
#include <linux/rtc.h>
#include <linux/math64.h>
+#include <linux/audit.h>

#include "ntp_internal.h"
#include "timekeeping_internal.h"
@@ -294,6 +295,8 @@ static inline s64 ntp_update_offset_fll(s64 offset64, long secs)

static void ntp_update_offset(long offset)
{
+ s64 old_offset = time_offset;
+ s64 old_freq = time_freq;
s64 freq_adj;
s64 offset64;
long secs;
@@ -342,6 +345,9 @@ static void ntp_update_offset(long offset)
time_freq = max(freq_adj, -MAXFREQ_SCALED);

time_offset = div_s64(offset64 << NTP_SCALE_SHIFT, NTP_INTERVAL_FREQ);
+
+ audit_ntp_adjust("offset", old_offset, time_offset);
+ audit_ntp_adjust("freq", old_freq, time_freq);
}

/**
@@ -669,21 +675,31 @@ static inline void process_adjtimex_modes(struct timex *txc,
struct timespec64 *ts,
s32 *time_tai)
{
- if (txc->modes & ADJ_STATUS)
- process_adj_status(txc, ts);
+ if (txc->modes & (ADJ_STATUS | ADJ_NANO | ADJ_MICRO)) {
+ int old_status = time_status;
+
+ if (txc->modes & ADJ_STATUS)
+ process_adj_status(txc, ts);

- if (txc->modes & ADJ_NANO)
- time_status |= STA_NANO;
+ if (txc->modes & ADJ_NANO)
+ time_status |= STA_NANO;

- if (txc->modes & ADJ_MICRO)
- time_status &= ~STA_NANO;
+ if (txc->modes & ADJ_MICRO)
+ time_status &= ~STA_NANO;
+
+ audit_ntp_adjust("status", old_status, time_status);
+ }

if (txc->modes & ADJ_FREQUENCY) {
+ s64 old_freq = time_freq;
+
time_freq = txc->freq * PPM_SCALE;
time_freq = min(time_freq, MAXFREQ_SCALED);
time_freq = max(time_freq, -MAXFREQ_SCALED);
/* update pps_freq */
pps_set_freq(time_freq);
+
+ audit_ntp_adjust("freq", old_freq, time_freq);
}

if (txc->modes & ADJ_MAXERROR)
@@ -700,14 +716,18 @@ static inline void process_adjtimex_modes(struct timex *txc,
time_constant = max(time_constant, 0l);
}

- if (txc->modes & ADJ_TAI && txc->constant > 0)
+ if (txc->modes & ADJ_TAI && txc->constant > 0) {
+ audit_ntp_adjust("tai", *time_tai, txc->constant);
*time_tai = txc->constant;
+ }

if (txc->modes & ADJ_OFFSET)
ntp_update_offset(txc->offset);

- if (txc->modes & ADJ_TICK)
+ if (txc->modes & ADJ_TICK) {
+ audit_ntp_adjust("tick", tick_usec, txc->tick);
tick_usec = txc->tick;
+ }

if (txc->modes & (ADJ_TICK|ADJ_FREQUENCY|ADJ_OFFSET))
ntp_update_frequency();
@@ -729,6 +749,8 @@ int __do_adjtimex(struct timex *txc, struct timespec64 *ts, s32 *time_tai)
/* adjtime() is independent from ntp_adjtime() */
time_adjust = txc->offset;
ntp_update_frequency();
+
+ audit_ntp_adjust("adjust", save_adjust, txc->offset);
}
txc->offset = save_adjust;
} else {
diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 4786df904c22..9089ac329e69 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -25,6 +25,7 @@
#include <linux/stop_machine.h>
#include <linux/pvclock_gtod.h>
#include <linux/compiler.h>
+#include <linux/audit.h>

#include "tick-internal.h"
#include "ntp_internal.h"
@@ -2308,6 +2309,8 @@ int do_adjtimex(struct timex *txc)
ret = timekeeping_inject_offset(&delta);
if (ret)
return ret;
+
+ audit_tk_injoffset(delta);
}

getnstimeofday64(&ts);
--
2.17.1


2018-08-24 18:35:00

by John Stultz

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Fri, Aug 24, 2018 at 5:00 AM, Ondrej Mosnacek <[email protected]> wrote:
> This patch adds two auxiliary record types that will be used to annotate
> the adjtimex SYSCALL records with the NTP/timekeeping values that have
> been changed.
>
> Next, it adds two functions to the audit interface:
> - audit_tk_injoffset(), which will be called whenever a timekeeping
> offset is injected by a syscall from userspace,
> - audit_ntp_adjust(), which will be called whenever an NTP internal
> variable is changed by a syscall from userspace.
>
> Quick reference for the fields of the new records:
> AUDIT_TIME_INJOFFSET
> sec - the 'seconds' part of the offset
> nsec - the 'nanoseconds' part of the offset
> AUDIT_TIME_ADJNTPVAL
> op - which value was adjusted:
> offset - corresponding to the time_offset variable
> freq - corresponding to the time_freq variable
> status - corresponding to the time_status variable
> adjust - corresponding to the time_adjust variable
> tick - corresponding to the tick_usec variable
> tai - corresponding to the timekeeping's TAI offset
> old - the old value
> new - the new value
>
> Signed-off-by: Ondrej Mosnacek <[email protected]>
> ---
> include/linux/audit.h | 21 +++++++++++++++++++++
> include/uapi/linux/audit.h | 2 ++
> kernel/auditsc.c | 15 +++++++++++++++
> 3 files changed, 38 insertions(+)
>
> diff --git a/include/linux/audit.h b/include/linux/audit.h
> index 9334fbef7bae..0d084d4b4042 100644
> --- a/include/linux/audit.h
> +++ b/include/linux/audit.h
> @@ -26,6 +26,7 @@
> #include <linux/sched.h>
> #include <linux/ptrace.h>
> #include <uapi/linux/audit.h>
> +#include <uapi/linux/timex.h>
>
> #define AUDIT_INO_UNSET ((unsigned long)-1)
> #define AUDIT_DEV_UNSET ((dev_t)-1)
> @@ -356,6 +357,8 @@ extern void __audit_log_capset(const struct cred *new, const struct cred *old);
> extern void __audit_mmap_fd(int fd, int flags);
> extern void __audit_log_kern_module(char *name);
> extern void __audit_fanotify(unsigned int response);
> +extern void __audit_tk_injoffset(struct timespec64 offset);
> +extern void __audit_ntp_adjust(const char *type, s64 oldval, s64 newval);
>
> static inline void audit_ipc_obj(struct kern_ipc_perm *ipcp)
> {
> @@ -458,6 +461,18 @@ static inline void audit_fanotify(unsigned int response)
> __audit_fanotify(response);
> }
>
> +static inline void audit_tk_injoffset(struct timespec64 offset)
> +{
> + if (!audit_dummy_context())
> + __audit_tk_injoffset(offset);
> +}
> +
> +static inline void audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> +{
> + if (!audit_dummy_context())
> + __audit_ntp_adjust(type, oldval, newval);
> +}
> +
> extern int audit_n_rules;
> extern int audit_signals;
> #else /* CONFIG_AUDITSYSCALL */
> @@ -584,6 +599,12 @@ static inline void audit_log_kern_module(char *name)
> static inline void audit_fanotify(unsigned int response)
> { }
>
> +static inline void audit_tk_injoffset(struct timespec64 offset)
> +{ }
> +
> +static inline void audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> +{ }
> +
> static inline void audit_ptrace(struct task_struct *t)
> { }
> #define audit_n_rules 0
> diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> index 4e3eaba84175..242ce562b41a 100644
> --- a/include/uapi/linux/audit.h
> +++ b/include/uapi/linux/audit.h
> @@ -114,6 +114,8 @@
> #define AUDIT_REPLACE 1329 /* Replace auditd if this packet unanswerd */
> #define AUDIT_KERN_MODULE 1330 /* Kernel Module events */
> #define AUDIT_FANOTIFY 1331 /* Fanotify access decision */
> +#define AUDIT_TIME_INJOFFSET 1332 /* Timekeeping offset injected */
> +#define AUDIT_TIME_ADJNTPVAL 1333 /* NTP value adjustment */
>
> #define AUDIT_AVC 1400 /* SE Linux avc denial or grant */
> #define AUDIT_SELINUX_ERR 1401 /* Internal SE Linux Errors */
> diff --git a/kernel/auditsc.c b/kernel/auditsc.c
> index fb207466e99b..d355d32d9765 100644
> --- a/kernel/auditsc.c
> +++ b/kernel/auditsc.c
> @@ -2422,6 +2422,21 @@ void __audit_fanotify(unsigned int response)
> AUDIT_FANOTIFY, "resp=%u", response);
> }
>
> +/* We need to allocate with GFP_ATOMIC here, since these two functions will be
> + * called while holding the timekeeping lock: */
> +void __audit_tk_injoffset(struct timespec64 offset)
> +{
> + audit_log(audit_context(), GFP_ATOMIC, AUDIT_TIME_INJOFFSET,
> + "sec=%lli nsec=%li", (long long)offset.tv_sec, offset.tv_nsec);
> +}
> +
> +void __audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> +{
> + audit_log(audit_context(), GFP_ATOMIC, AUDIT_TIME_ADJNTPVAL,
> + "op=%s old=%lli new=%lli", type,
> + (long long)oldval, (long long)newval);
> +}

So it looks like you've been careful here, but just want to make sure,
nothing in the audit_log path calls anything that might possibly call
back into timekeeping code? We've had a number of issues over time
where debug calls out to other subsystems end up getting tweaked to
add some timestamping and we deadlock. :)

One approach we've done to make sure we don't create a trap for future
changes in other subsystems, is avoid calling into other subsystems
until later when we've dropped the locks, (see clock_was_set). Its a
little rough for some of the things done deep in the ntp code, but
might be an extra cautious approach to try.

thanks
-john

2018-08-24 19:52:57

by Richard Guy Briggs

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 2/2] timekeeping/ntp: Audit clock/NTP params adjustments

On 2018-08-24 14:00, Ondrej Mosnacek wrote:
> This patch adds logging of all attempts to either inject an offset into
> the clock (producing an AUDIT_TIME_INJOFFSET record) or adjust an NTP
> parameter (producing an AUDIT_TIME_ADJNTPVAL record).

I thought I saw it suggested earlier in one of the replies to a previous
revision of the patchset to separate the two types of records with their
calling circumstances. The inj-offset bits could stand alone in their
own patch leaving all the rest in its own patch. The record numbers and
examples are easier to offer when given together, but they aren't as
clear they are indepnendent records and callers. That way, each patch
stands on its own. (more below)

> For reference, running the following commands:
>
> auditctl -D
> auditctl -a exit,always -F arch=b64 -S adjtimex
> chronyd -q
>
> produces audit records like this:
>
> type=TIME_ADJNTPVAL msg=audit(1530616044.507:5): op=adjust old=0 new=0
> type=SYSCALL msg=audit(1530616044.507:5): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=0 a2=4 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> type=PROCTITLE msg=audit(1530616044.507:5): proctitle=6368726F6E7964002D71
> type=SYSCALL msg=audit(1530616044.507:6): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=1 a2=1 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> type=PROCTITLE msg=audit(1530616044.507:6): proctitle=6368726F6E7964002D71
> type=TIME_INJOFFSET msg=audit(1530616044.507:7): sec=0 nsec=0
> type=TIME_ADJNTPVAL msg=audit(1530616044.507:7): op=status old=64 new=8256
> type=SYSCALL msg=audit(1530616044.507:7): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=1 a2=1 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> type=PROCTITLE msg=audit(1530616044.507:7): proctitle=6368726F6E7964002D71
> type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=status old=8256 new=8257
> type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=offset old=0 new=0
> type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=freq old=0 new=0
> type=SYSCALL msg=audit(1530616044.507:8): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ab0 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> type=PROCTITLE msg=audit(1530616044.507:8): proctitle=6368726F6E7964002D71
> type=TIME_ADJNTPVAL msg=audit(1530616044.507:9): op=status old=8257 new=64
> type=SYSCALL msg=audit(1530616044.507:9): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ab0 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> type=PROCTITLE msg=audit(1530616044.507:9): proctitle=6368726F6E7964002D71
> type=SYSCALL msg=audit(1530616044.507:10): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78a70 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> type=PROCTITLE msg=audit(1530616044.507:10): proctitle=6368726F6E7964002D71
> type=TIME_ADJNTPVAL msg=audit(1530616044.511:11): op=freq old=0 new=49180377088000
> type=TIME_ADJNTPVAL msg=audit(1530616044.511:11): op=tick old=10000 new=10000
> type=SYSCALL msg=audit(1530616044.511:11): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ad0 a1=0 a2=2710 a3=f42f82a800000 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> type=PROCTITLE msg=audit(1530616044.511:11): proctitle=6368726F6E7964002D71
> type=TIME_ADJNTPVAL msg=audit(1530616044.521:12): op=status old=64 new=64
> type=SYSCALL msg=audit(1530616044.521:12): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78b40 a1=1 a2=40 a3=f91f6ef84fbab items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> type=PROCTITLE msg=audit(1530616044.521:12): proctitle=6368726F6E7964002D71
> type=TIME_INJOFFSET msg=audit(1530616049.652:13): sec=-16 nsec=124887145
> type=TIME_ADJNTPVAL msg=audit(1530616049.652:13): op=status old=64 new=8256
> type=SYSCALL msg=audit(1530616049.652:13): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78270 a1=1 a2=fffffffffffffff0 a3=137b828205ca12 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> type=PROCTITLE msg=audit(1530616049.652:13): proctitle=6368726F6E7964002D71
> type=TIME_ADJNTPVAL msg=audit(1530616033.783:14): op=freq old=49180377088000 new=49180377088000
> type=TIME_ADJNTPVAL msg=audit(1530616033.783:14): op=tick old=10000 new=10000
> type=SYSCALL msg=audit(1530616033.783:14): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78bc0 a1=0 a2=2710 a3=0 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> type=PROCTITLE msg=audit(1530616033.783:14): proctitle=6368726F6E7964002D71
>
> The chronyd command that produced the above records executed the
> following adjtimex(2) syscalls (as per strace output):
>
> adjtimex({modes=ADJ_OFFSET|0x8000, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507215}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> adjtimex({modes=ADJ_MAXERROR, offset=0, freq=0, maxerror=0, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507438}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> adjtimex({modes=ADJ_SETOFFSET|ADJ_NANO, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507604737}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> adjtimex({modes=ADJ_OFFSET|ADJ_STATUS, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_PLL|STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507698330}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> adjtimex({modes=ADJ_STATUS, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507792}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> adjtimex({modes=0, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=508000}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> adjtimex({modes=ADJ_FREQUENCY|ADJ_TICK, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=512146}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> adjtimex({modes=ADJ_MAXERROR|ADJ_ESTERROR|ADJ_STATUS, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=522506}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> adjtimex({modes=ADJ_SETOFFSET|ADJ_NANO, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616033, tv_usec=778717675}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> adjtimex({modes=ADJ_FREQUENCY|ADJ_TICK, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616033, tv_usec=784644657}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>
> (The struct timex fields above are from *after* the syscall was
> executed, so they contain the current (new) values as set from the
> kernel, except of the 'modes' field, which contains the original value
> sent by the caller.)
>
> The changes to the time_maxerror, time_esterror, and time_constant
> variables are not logged, as these are not important for security.
>
> Note that the records are emitted even when the actual value does not
> change (i.e. when there is an explicit attempt to change a value, but
> the new value equals the old one).
>
> An overview of changes that can be done via adjtimex(2) (based on
> information from Miroslav Lichvar) and whether they are audited:
> timekeeping_inject_offset() -- injects offset directly into system
> time (AUDITED)
> __timekeeping_set_tai_offset() -- sets the offset from the
> International Atomic Time
> (AUDITED)
> NTP variables:
> time_offset -- can adjust the clock by up to 0.5 seconds per call
> and also speed it up or slow down by up to about
> 0.05% (43 seconds per day) (AUDITED)
> time_freq -- can speed up or slow down by up to about 0.05%
> time_status -- can insert/delete leap seconds and it also enables/
> disables synchronization of the hardware real-time
> clock (AUDITED)
> time_maxerror, time_esterror -- change error estimates used to
> inform userspace applications
> (NOT AUDITED)
> time_constant -- controls the speed of the clock adjustments that
> are made when time_offset is set (NOT AUDITED)
> time_adjust -- can temporarily speed up or slow down the clock by up
> to 0.05% (AUDITED)
> tick_usec -- a more extreme version of time_freq; can speed up or
> slow down the clock by up to 10% (AUDITED)
>
> Cc: Miroslav Lichvar <[email protected]>
> Signed-off-by: Ondrej Mosnacek <[email protected]>
> ---
> kernel/time/ntp.c | 38 ++++++++++++++++++++++++++++++--------
> kernel/time/timekeeping.c | 3 +++
> 2 files changed, 33 insertions(+), 8 deletions(-)
>
> diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
> index a09ded765f6c..f96c6d326aae 100644
> --- a/kernel/time/ntp.c
> +++ b/kernel/time/ntp.c
> @@ -18,6 +18,7 @@
> #include <linux/module.h>
> #include <linux/rtc.h>
> #include <linux/math64.h>
> +#include <linux/audit.h>
>
> #include "ntp_internal.h"
> #include "timekeeping_internal.h"
> @@ -294,6 +295,8 @@ static inline s64 ntp_update_offset_fll(s64 offset64, long secs)
>
> static void ntp_update_offset(long offset)
> {
> + s64 old_offset = time_offset;
> + s64 old_freq = time_freq;
> s64 freq_adj;
> s64 offset64;
> long secs;
> @@ -342,6 +345,9 @@ static void ntp_update_offset(long offset)
> time_freq = max(freq_adj, -MAXFREQ_SCALED);
>
> time_offset = div_s64(offset64 << NTP_SCALE_SHIFT, NTP_INTERVAL_FREQ);
> +
> + audit_ntp_adjust("offset", old_offset, time_offset);
> + audit_ntp_adjust("freq", old_freq, time_freq);
> }
>
> /**
> @@ -669,21 +675,31 @@ static inline void process_adjtimex_modes(struct timex *txc,
> struct timespec64 *ts,
> s32 *time_tai)
> {
> - if (txc->modes & ADJ_STATUS)
> - process_adj_status(txc, ts);
> + if (txc->modes & (ADJ_STATUS | ADJ_NANO | ADJ_MICRO)) {
> + int old_status = time_status;
> +
> + if (txc->modes & ADJ_STATUS)
> + process_adj_status(txc, ts);
>
> - if (txc->modes & ADJ_NANO)
> - time_status |= STA_NANO;
> + if (txc->modes & ADJ_NANO)
> + time_status |= STA_NANO;
>
> - if (txc->modes & ADJ_MICRO)
> - time_status &= ~STA_NANO;
> + if (txc->modes & ADJ_MICRO)
> + time_status &= ~STA_NANO;
> +
> + audit_ntp_adjust("status", old_status, time_status);
> + }
>
> if (txc->modes & ADJ_FREQUENCY) {
> + s64 old_freq = time_freq;
> +
> time_freq = txc->freq * PPM_SCALE;
> time_freq = min(time_freq, MAXFREQ_SCALED);
> time_freq = max(time_freq, -MAXFREQ_SCALED);
> /* update pps_freq */
> pps_set_freq(time_freq);
> +
> + audit_ntp_adjust("freq", old_freq, time_freq);
> }
>
> if (txc->modes & ADJ_MAXERROR)
> @@ -700,14 +716,18 @@ static inline void process_adjtimex_modes(struct timex *txc,
> time_constant = max(time_constant, 0l);
> }
>
> - if (txc->modes & ADJ_TAI && txc->constant > 0)
> + if (txc->modes & ADJ_TAI && txc->constant > 0) {
> + audit_ntp_adjust("tai", *time_tai, txc->constant);
> *time_tai = txc->constant;
> + }

It appears this time_tai use of "constant" is different than
time_constant, the former not mentioned by Miroslav Lichvar. What is it
and is it important to log for security? It sounds like it is
important.

> if (txc->modes & ADJ_OFFSET)
> ntp_update_offset(txc->offset);
>
> - if (txc->modes & ADJ_TICK)
> + if (txc->modes & ADJ_TICK) {
> + audit_ntp_adjust("tick", tick_usec, txc->tick);
> tick_usec = txc->tick;
> + }
>
> if (txc->modes & (ADJ_TICK|ADJ_FREQUENCY|ADJ_OFFSET))
> ntp_update_frequency();
> @@ -729,6 +749,8 @@ int __do_adjtimex(struct timex *txc, struct timespec64 *ts, s32 *time_tai)
> /* adjtime() is independent from ntp_adjtime() */
> time_adjust = txc->offset;
> ntp_update_frequency();
> +
> + audit_ntp_adjust("adjust", save_adjust, txc->offset);
> }
> txc->offset = save_adjust;
> } else {
> diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
> index 4786df904c22..9089ac329e69 100644
> --- a/kernel/time/timekeeping.c
> +++ b/kernel/time/timekeeping.c
> @@ -25,6 +25,7 @@
> #include <linux/stop_machine.h>
> #include <linux/pvclock_gtod.h>
> #include <linux/compiler.h>
> +#include <linux/audit.h>
>
> #include "tick-internal.h"
> #include "ntp_internal.h"
> @@ -2308,6 +2309,8 @@ int do_adjtimex(struct timex *txc)
> ret = timekeeping_inject_offset(&delta);
> if (ret)
> return ret;
> +
> + audit_tk_injoffset(delta);
> }
>
> getnstimeofday64(&ts);
> --
> 2.17.1
>
> --
> Linux-audit mailing list
> [email protected]
> https://www.redhat.com/mailman/listinfo/linux-audit

- RGB

--
Richard Guy Briggs <[email protected]>
Sr. S/W Engineer, Kernel Security, Base Operating Systems
Remote, Ottawa, Red Hat Canada
IRC: rgb, SunRaycer
Voice: +1.647.777.2635, Internal: (81) 32635

2018-08-24 20:22:09

by John Stultz

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 2/2] timekeeping/ntp: Audit clock/NTP params adjustments

On Fri, Aug 24, 2018 at 12:47 PM, Richard Guy Briggs <[email protected]> wrote:
> On 2018-08-24 14:00, Ondrej Mosnacek wrote:
>> This patch adds logging of all attempts to either inject an offset into
>> the clock (producing an AUDIT_TIME_INJOFFSET record) or adjust an NTP
>> parameter (producing an AUDIT_TIME_ADJNTPVAL record).
>
> I thought I saw it suggested earlier in one of the replies to a previous
> revision of the patchset to separate the two types of records with their
> calling circumstances. The inj-offset bits could stand alone in their
> own patch leaving all the rest in its own patch. The record numbers and
> examples are easier to offer when given together, but they aren't as
> clear they are indepnendent records and callers. That way, each patch
> stands on its own. (more below)
>
>> For reference, running the following commands:
>>
>> auditctl -D
>> auditctl -a exit,always -F arch=b64 -S adjtimex
>> chronyd -q
>>
>> produces audit records like this:
>>
>> type=TIME_ADJNTPVAL msg=audit(1530616044.507:5): op=adjust old=0 new=0
>> type=SYSCALL msg=audit(1530616044.507:5): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=0 a2=4 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
>> type=PROCTITLE msg=audit(1530616044.507:5): proctitle=6368726F6E7964002D71
>> type=SYSCALL msg=audit(1530616044.507:6): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=1 a2=1 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
>> type=PROCTITLE msg=audit(1530616044.507:6): proctitle=6368726F6E7964002D71
>> type=TIME_INJOFFSET msg=audit(1530616044.507:7): sec=0 nsec=0
>> type=TIME_ADJNTPVAL msg=audit(1530616044.507:7): op=status old=64 new=8256
>> type=SYSCALL msg=audit(1530616044.507:7): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=1 a2=1 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
>> type=PROCTITLE msg=audit(1530616044.507:7): proctitle=6368726F6E7964002D71
>> type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=status old=8256 new=8257
>> type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=offset old=0 new=0
>> type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=freq old=0 new=0
>> type=SYSCALL msg=audit(1530616044.507:8): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ab0 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
>> type=PROCTITLE msg=audit(1530616044.507:8): proctitle=6368726F6E7964002D71
>> type=TIME_ADJNTPVAL msg=audit(1530616044.507:9): op=status old=8257 new=64
>> type=SYSCALL msg=audit(1530616044.507:9): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ab0 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
>> type=PROCTITLE msg=audit(1530616044.507:9): proctitle=6368726F6E7964002D71
>> type=SYSCALL msg=audit(1530616044.507:10): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78a70 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
>> type=PROCTITLE msg=audit(1530616044.507:10): proctitle=6368726F6E7964002D71
>> type=TIME_ADJNTPVAL msg=audit(1530616044.511:11): op=freq old=0 new=49180377088000
>> type=TIME_ADJNTPVAL msg=audit(1530616044.511:11): op=tick old=10000 new=10000
>> type=SYSCALL msg=audit(1530616044.511:11): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ad0 a1=0 a2=2710 a3=f42f82a800000 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
>> type=PROCTITLE msg=audit(1530616044.511:11): proctitle=6368726F6E7964002D71
>> type=TIME_ADJNTPVAL msg=audit(1530616044.521:12): op=status old=64 new=64
>> type=SYSCALL msg=audit(1530616044.521:12): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78b40 a1=1 a2=40 a3=f91f6ef84fbab items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
>> type=PROCTITLE msg=audit(1530616044.521:12): proctitle=6368726F6E7964002D71
>> type=TIME_INJOFFSET msg=audit(1530616049.652:13): sec=-16 nsec=124887145
>> type=TIME_ADJNTPVAL msg=audit(1530616049.652:13): op=status old=64 new=8256
>> type=SYSCALL msg=audit(1530616049.652:13): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78270 a1=1 a2=fffffffffffffff0 a3=137b828205ca12 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
>> type=PROCTITLE msg=audit(1530616049.652:13): proctitle=6368726F6E7964002D71
>> type=TIME_ADJNTPVAL msg=audit(1530616033.783:14): op=freq old=49180377088000 new=49180377088000
>> type=TIME_ADJNTPVAL msg=audit(1530616033.783:14): op=tick old=10000 new=10000
>> type=SYSCALL msg=audit(1530616033.783:14): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78bc0 a1=0 a2=2710 a3=0 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
>> type=PROCTITLE msg=audit(1530616033.783:14): proctitle=6368726F6E7964002D71
>>
>> The chronyd command that produced the above records executed the
>> following adjtimex(2) syscalls (as per strace output):
>>
>> adjtimex({modes=ADJ_OFFSET|0x8000, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507215}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>> adjtimex({modes=ADJ_MAXERROR, offset=0, freq=0, maxerror=0, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507438}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>> adjtimex({modes=ADJ_SETOFFSET|ADJ_NANO, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507604737}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>> adjtimex({modes=ADJ_OFFSET|ADJ_STATUS, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_PLL|STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507698330}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>> adjtimex({modes=ADJ_STATUS, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507792}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>> adjtimex({modes=0, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=508000}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>> adjtimex({modes=ADJ_FREQUENCY|ADJ_TICK, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=512146}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>> adjtimex({modes=ADJ_MAXERROR|ADJ_ESTERROR|ADJ_STATUS, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=522506}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>> adjtimex({modes=ADJ_SETOFFSET|ADJ_NANO, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616033, tv_usec=778717675}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>> adjtimex({modes=ADJ_FREQUENCY|ADJ_TICK, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616033, tv_usec=784644657}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
>>
>> (The struct timex fields above are from *after* the syscall was
>> executed, so they contain the current (new) values as set from the
>> kernel, except of the 'modes' field, which contains the original value
>> sent by the caller.)
>>
>> The changes to the time_maxerror, time_esterror, and time_constant
>> variables are not logged, as these are not important for security.
>>
>> Note that the records are emitted even when the actual value does not
>> change (i.e. when there is an explicit attempt to change a value, but
>> the new value equals the old one).
>>
>> An overview of changes that can be done via adjtimex(2) (based on
>> information from Miroslav Lichvar) and whether they are audited:
>> timekeeping_inject_offset() -- injects offset directly into system
>> time (AUDITED)
>> __timekeeping_set_tai_offset() -- sets the offset from the
>> International Atomic Time
>> (AUDITED)
>> NTP variables:
>> time_offset -- can adjust the clock by up to 0.5 seconds per call
>> and also speed it up or slow down by up to about
>> 0.05% (43 seconds per day) (AUDITED)
>> time_freq -- can speed up or slow down by up to about 0.05%
>> time_status -- can insert/delete leap seconds and it also enables/
>> disables synchronization of the hardware real-time
>> clock (AUDITED)
>> time_maxerror, time_esterror -- change error estimates used to
>> inform userspace applications
>> (NOT AUDITED)
>> time_constant -- controls the speed of the clock adjustments that
>> are made when time_offset is set (NOT AUDITED)
>> time_adjust -- can temporarily speed up or slow down the clock by up
>> to 0.05% (AUDITED)
>> tick_usec -- a more extreme version of time_freq; can speed up or
>> slow down the clock by up to 10% (AUDITED)
>>
>> Cc: Miroslav Lichvar <[email protected]>
>> Signed-off-by: Ondrej Mosnacek <[email protected]>
>> ---
>> kernel/time/ntp.c | 38 ++++++++++++++++++++++++++++++--------
>> kernel/time/timekeeping.c | 3 +++
>> 2 files changed, 33 insertions(+), 8 deletions(-)
>>
>> diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
>> index a09ded765f6c..f96c6d326aae 100644
>> --- a/kernel/time/ntp.c
>> +++ b/kernel/time/ntp.c
>> @@ -18,6 +18,7 @@
>> #include <linux/module.h>
>> #include <linux/rtc.h>
>> #include <linux/math64.h>
>> +#include <linux/audit.h>
>>
>> #include "ntp_internal.h"
>> #include "timekeeping_internal.h"
>> @@ -294,6 +295,8 @@ static inline s64 ntp_update_offset_fll(s64 offset64, long secs)
>>
>> static void ntp_update_offset(long offset)
>> {
>> + s64 old_offset = time_offset;
>> + s64 old_freq = time_freq;
>> s64 freq_adj;
>> s64 offset64;
>> long secs;
>> @@ -342,6 +345,9 @@ static void ntp_update_offset(long offset)
>> time_freq = max(freq_adj, -MAXFREQ_SCALED);
>>
>> time_offset = div_s64(offset64 << NTP_SCALE_SHIFT, NTP_INTERVAL_FREQ);
>> +
>> + audit_ntp_adjust("offset", old_offset, time_offset);
>> + audit_ntp_adjust("freq", old_freq, time_freq);
>> }
>>
>> /**
>> @@ -669,21 +675,31 @@ static inline void process_adjtimex_modes(struct timex *txc,
>> struct timespec64 *ts,
>> s32 *time_tai)
>> {
>> - if (txc->modes & ADJ_STATUS)
>> - process_adj_status(txc, ts);
>> + if (txc->modes & (ADJ_STATUS | ADJ_NANO | ADJ_MICRO)) {
>> + int old_status = time_status;
>> +
>> + if (txc->modes & ADJ_STATUS)
>> + process_adj_status(txc, ts);
>>
>> - if (txc->modes & ADJ_NANO)
>> - time_status |= STA_NANO;
>> + if (txc->modes & ADJ_NANO)
>> + time_status |= STA_NANO;
>>
>> - if (txc->modes & ADJ_MICRO)
>> - time_status &= ~STA_NANO;
>> + if (txc->modes & ADJ_MICRO)
>> + time_status &= ~STA_NANO;
>> +
>> + audit_ntp_adjust("status", old_status, time_status);
>> + }
>>
>> if (txc->modes & ADJ_FREQUENCY) {
>> + s64 old_freq = time_freq;
>> +
>> time_freq = txc->freq * PPM_SCALE;
>> time_freq = min(time_freq, MAXFREQ_SCALED);
>> time_freq = max(time_freq, -MAXFREQ_SCALED);
>> /* update pps_freq */
>> pps_set_freq(time_freq);
>> +
>> + audit_ntp_adjust("freq", old_freq, time_freq);
>> }
>>
>> if (txc->modes & ADJ_MAXERROR)
>> @@ -700,14 +716,18 @@ static inline void process_adjtimex_modes(struct timex *txc,
>> time_constant = max(time_constant, 0l);
>> }
>>
>> - if (txc->modes & ADJ_TAI && txc->constant > 0)
>> + if (txc->modes & ADJ_TAI && txc->constant > 0) {
>> + audit_ntp_adjust("tai", *time_tai, txc->constant);
>> *time_tai = txc->constant;
>> + }
>
> It appears this time_tai use of "constant" is different than
> time_constant, the former not mentioned by Miroslav Lichvar. What is it
> and is it important to log for security? It sounds like it is
> important.

From the adjtimex man page:
ADJ_TAI (since Linux 2.6.26)
Set TAI (Atomic International Time) offset from buf->constant.

thanks
-john

2018-08-27 07:51:50

by Miroslav Lichvar

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Fri, Aug 24, 2018 at 02:00:00PM +0200, Ondrej Mosnacek wrote:
> This patch adds two auxiliary record types that will be used to annotate
> the adjtimex SYSCALL records with the NTP/timekeeping values that have
> been changed.

It seems the "adjust" function intentionally logs also calls/modes
that don't actually change anything. Can you please explain it a bit
in the message?

NTP/PTP daemons typically don't read the adjtimex values in a normal
operation and overwrite them on each update, even if they don't
change. If the audit function checked that oldval != newval, the
number of messages would be reduced and it might be easier to follow.

--
Miroslav Lichvar

2018-08-27 08:31:22

by Ondrej Mosnacek

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Fri, Aug 24, 2018 at 8:33 PM John Stultz <[email protected]> wrote:
> On Fri, Aug 24, 2018 at 5:00 AM, Ondrej Mosnacek <[email protected]> wrote:
> > This patch adds two auxiliary record types that will be used to annotate
> > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > been changed.
> >
> > Next, it adds two functions to the audit interface:
> > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > offset is injected by a syscall from userspace,
> > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > variable is changed by a syscall from userspace.
> >
> > Quick reference for the fields of the new records:
> > AUDIT_TIME_INJOFFSET
> > sec - the 'seconds' part of the offset
> > nsec - the 'nanoseconds' part of the offset
> > AUDIT_TIME_ADJNTPVAL
> > op - which value was adjusted:
> > offset - corresponding to the time_offset variable
> > freq - corresponding to the time_freq variable
> > status - corresponding to the time_status variable
> > adjust - corresponding to the time_adjust variable
> > tick - corresponding to the tick_usec variable
> > tai - corresponding to the timekeeping's TAI offset
> > old - the old value
> > new - the new value
> >
> > Signed-off-by: Ondrej Mosnacek <[email protected]>
> > ---
> > include/linux/audit.h | 21 +++++++++++++++++++++
> > include/uapi/linux/audit.h | 2 ++
> > kernel/auditsc.c | 15 +++++++++++++++
> > 3 files changed, 38 insertions(+)
> >
> > diff --git a/include/linux/audit.h b/include/linux/audit.h
> > index 9334fbef7bae..0d084d4b4042 100644
> > --- a/include/linux/audit.h
> > +++ b/include/linux/audit.h
> > @@ -26,6 +26,7 @@
> > #include <linux/sched.h>
> > #include <linux/ptrace.h>
> > #include <uapi/linux/audit.h>
> > +#include <uapi/linux/timex.h>
> >
> > #define AUDIT_INO_UNSET ((unsigned long)-1)
> > #define AUDIT_DEV_UNSET ((dev_t)-1)
> > @@ -356,6 +357,8 @@ extern void __audit_log_capset(const struct cred *new, const struct cred *old);
> > extern void __audit_mmap_fd(int fd, int flags);
> > extern void __audit_log_kern_module(char *name);
> > extern void __audit_fanotify(unsigned int response);
> > +extern void __audit_tk_injoffset(struct timespec64 offset);
> > +extern void __audit_ntp_adjust(const char *type, s64 oldval, s64 newval);
> >
> > static inline void audit_ipc_obj(struct kern_ipc_perm *ipcp)
> > {
> > @@ -458,6 +461,18 @@ static inline void audit_fanotify(unsigned int response)
> > __audit_fanotify(response);
> > }
> >
> > +static inline void audit_tk_injoffset(struct timespec64 offset)
> > +{
> > + if (!audit_dummy_context())
> > + __audit_tk_injoffset(offset);
> > +}
> > +
> > +static inline void audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> > +{
> > + if (!audit_dummy_context())
> > + __audit_ntp_adjust(type, oldval, newval);
> > +}
> > +
> > extern int audit_n_rules;
> > extern int audit_signals;
> > #else /* CONFIG_AUDITSYSCALL */
> > @@ -584,6 +599,12 @@ static inline void audit_log_kern_module(char *name)
> > static inline void audit_fanotify(unsigned int response)
> > { }
> >
> > +static inline void audit_tk_injoffset(struct timespec64 offset)
> > +{ }
> > +
> > +static inline void audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> > +{ }
> > +
> > static inline void audit_ptrace(struct task_struct *t)
> > { }
> > #define audit_n_rules 0
> > diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> > index 4e3eaba84175..242ce562b41a 100644
> > --- a/include/uapi/linux/audit.h
> > +++ b/include/uapi/linux/audit.h
> > @@ -114,6 +114,8 @@
> > #define AUDIT_REPLACE 1329 /* Replace auditd if this packet unanswerd */
> > #define AUDIT_KERN_MODULE 1330 /* Kernel Module events */
> > #define AUDIT_FANOTIFY 1331 /* Fanotify access decision */
> > +#define AUDIT_TIME_INJOFFSET 1332 /* Timekeeping offset injected */
> > +#define AUDIT_TIME_ADJNTPVAL 1333 /* NTP value adjustment */
> >
> > #define AUDIT_AVC 1400 /* SE Linux avc denial or grant */
> > #define AUDIT_SELINUX_ERR 1401 /* Internal SE Linux Errors */
> > diff --git a/kernel/auditsc.c b/kernel/auditsc.c
> > index fb207466e99b..d355d32d9765 100644
> > --- a/kernel/auditsc.c
> > +++ b/kernel/auditsc.c
> > @@ -2422,6 +2422,21 @@ void __audit_fanotify(unsigned int response)
> > AUDIT_FANOTIFY, "resp=%u", response);
> > }
> >
> > +/* We need to allocate with GFP_ATOMIC here, since these two functions will be
> > + * called while holding the timekeeping lock: */
> > +void __audit_tk_injoffset(struct timespec64 offset)
> > +{
> > + audit_log(audit_context(), GFP_ATOMIC, AUDIT_TIME_INJOFFSET,
> > + "sec=%lli nsec=%li", (long long)offset.tv_sec, offset.tv_nsec);
> > +}
> > +
> > +void __audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> > +{
> > + audit_log(audit_context(), GFP_ATOMIC, AUDIT_TIME_ADJNTPVAL,
> > + "op=%s old=%lli new=%lli", type,
> > + (long long)oldval, (long long)newval);
> > +}
>
> So it looks like you've been careful here, but just want to make sure,
> nothing in the audit_log path calls anything that might possibly call
> back into timekeeping code? We've had a number of issues over time
> where debug calls out to other subsystems end up getting tweaked to
> add some timestamping and we deadlock. :)

Hm, that's a good point... AFAIK, the only place where audit_log()
might call into timekeeping is when it captures the timestamp for the
record (via ktime_get_coarse_real_ts64()), but this is only called
when the functions are called outside of a syscall and that should
never happen here. I'm thinking I could harden this more by returning
early from these functions if WARN_ON_ONCE(!audit_context() ||
!audit_context()->in_syscall)... It is not a perfect solution, but at
least there will be something in the code reminding us about this
issue.

>
> One approach we've done to make sure we don't create a trap for future
> changes in other subsystems, is avoid calling into other subsystems
> until later when we've dropped the locks, (see clock_was_set). Its a
> little rough for some of the things done deep in the ntp code, but
> might be an extra cautious approach to try.

I'm afraid that delaying the audit_* calls would complicate things too
much here... Paul/Richard, any thoughts?

--
Ondrej Mosnacek <omosnace at redhat dot com>
Associate Software Engineer, Security Technologies
Red Hat, Inc.

2018-08-27 09:15:25

by Ondrej Mosnacek

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Mon, Aug 27, 2018 at 9:50 AM Miroslav Lichvar <[email protected]> wrote:
> On Fri, Aug 24, 2018 at 02:00:00PM +0200, Ondrej Mosnacek wrote:
> > This patch adds two auxiliary record types that will be used to annotate
> > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > been changed.
>
> It seems the "adjust" function intentionally logs also calls/modes
> that don't actually change anything. Can you please explain it a bit
> in the message?
>
> NTP/PTP daemons typically don't read the adjtimex values in a normal
> operation and overwrite them on each update, even if they don't
> change. If the audit function checked that oldval != newval, the
> number of messages would be reduced and it might be easier to follow.

We actually want to log any attempt to change a value, as even an
intention to set/change something could be a hint that the process is
trying to do something bad (see discussion at [1]). There are valid
arguments both for and against this choice, but we have to pick one in
the end... Anyway, I should explain the reasoning in the commit
message better, right now it just states the fact without explanation
(in the second patch), thank you for pointing my attention to it.

[1] https://www.redhat.com/archives/linux-audit/2018-July/msg00061.html

--
Ondrej Mosnacek <omosnace at redhat dot com>
Associate Software Engineer, Security Technologies
Red Hat, Inc.

2018-08-27 11:36:46

by Ondrej Mosnacek

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 2/2] timekeeping/ntp: Audit clock/NTP params adjustments

On Fri, Aug 24, 2018 at 9:51 PM Richard Guy Briggs <[email protected]> wrote:
> On 2018-08-24 14:00, Ondrej Mosnacek wrote:
> > This patch adds logging of all attempts to either inject an offset into
> > the clock (producing an AUDIT_TIME_INJOFFSET record) or adjust an NTP
> > parameter (producing an AUDIT_TIME_ADJNTPVAL record).
>
> I thought I saw it suggested earlier in one of the replies to a previous
> revision of the patchset to separate the two types of records with their
> calling circumstances. The inj-offset bits could stand alone in their
> own patch leaving all the rest in its own patch. The record numbers and
> examples are easier to offer when given together, but they aren't as
> clear they are indepnendent records and callers. That way, each patch
> stands on its own. (more below)

Well, the idea of current split-up is to separate changes in different
subsystems. I would argue that the two record types are related enough
(and the diffs short enough) that it is worth keeping them together.

>
> > For reference, running the following commands:
> >
> > auditctl -D
> > auditctl -a exit,always -F arch=b64 -S adjtimex
> > chronyd -q
> >
> > produces audit records like this:
> >
> > type=TIME_ADJNTPVAL msg=audit(1530616044.507:5): op=adjust old=0 new=0
> > type=SYSCALL msg=audit(1530616044.507:5): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=0 a2=4 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > type=PROCTITLE msg=audit(1530616044.507:5): proctitle=6368726F6E7964002D71
> > type=SYSCALL msg=audit(1530616044.507:6): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=1 a2=1 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > type=PROCTITLE msg=audit(1530616044.507:6): proctitle=6368726F6E7964002D71
> > type=TIME_INJOFFSET msg=audit(1530616044.507:7): sec=0 nsec=0
> > type=TIME_ADJNTPVAL msg=audit(1530616044.507:7): op=status old=64 new=8256
> > type=SYSCALL msg=audit(1530616044.507:7): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=1 a2=1 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > type=PROCTITLE msg=audit(1530616044.507:7): proctitle=6368726F6E7964002D71
> > type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=status old=8256 new=8257
> > type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=offset old=0 new=0
> > type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=freq old=0 new=0
> > type=SYSCALL msg=audit(1530616044.507:8): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ab0 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > type=PROCTITLE msg=audit(1530616044.507:8): proctitle=6368726F6E7964002D71
> > type=TIME_ADJNTPVAL msg=audit(1530616044.507:9): op=status old=8257 new=64
> > type=SYSCALL msg=audit(1530616044.507:9): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ab0 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > type=PROCTITLE msg=audit(1530616044.507:9): proctitle=6368726F6E7964002D71
> > type=SYSCALL msg=audit(1530616044.507:10): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78a70 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > type=PROCTITLE msg=audit(1530616044.507:10): proctitle=6368726F6E7964002D71
> > type=TIME_ADJNTPVAL msg=audit(1530616044.511:11): op=freq old=0 new=49180377088000
> > type=TIME_ADJNTPVAL msg=audit(1530616044.511:11): op=tick old=10000 new=10000
> > type=SYSCALL msg=audit(1530616044.511:11): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ad0 a1=0 a2=2710 a3=f42f82a800000 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > type=PROCTITLE msg=audit(1530616044.511:11): proctitle=6368726F6E7964002D71
> > type=TIME_ADJNTPVAL msg=audit(1530616044.521:12): op=status old=64 new=64
> > type=SYSCALL msg=audit(1530616044.521:12): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78b40 a1=1 a2=40 a3=f91f6ef84fbab items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > type=PROCTITLE msg=audit(1530616044.521:12): proctitle=6368726F6E7964002D71
> > type=TIME_INJOFFSET msg=audit(1530616049.652:13): sec=-16 nsec=124887145
> > type=TIME_ADJNTPVAL msg=audit(1530616049.652:13): op=status old=64 new=8256
> > type=SYSCALL msg=audit(1530616049.652:13): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78270 a1=1 a2=fffffffffffffff0 a3=137b828205ca12 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > type=PROCTITLE msg=audit(1530616049.652:13): proctitle=6368726F6E7964002D71
> > type=TIME_ADJNTPVAL msg=audit(1530616033.783:14): op=freq old=49180377088000 new=49180377088000
> > type=TIME_ADJNTPVAL msg=audit(1530616033.783:14): op=tick old=10000 new=10000
> > type=SYSCALL msg=audit(1530616033.783:14): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78bc0 a1=0 a2=2710 a3=0 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > type=PROCTITLE msg=audit(1530616033.783:14): proctitle=6368726F6E7964002D71
> >
> > The chronyd command that produced the above records executed the
> > following adjtimex(2) syscalls (as per strace output):
> >
> > adjtimex({modes=ADJ_OFFSET|0x8000, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507215}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > adjtimex({modes=ADJ_MAXERROR, offset=0, freq=0, maxerror=0, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507438}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > adjtimex({modes=ADJ_SETOFFSET|ADJ_NANO, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507604737}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > adjtimex({modes=ADJ_OFFSET|ADJ_STATUS, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_PLL|STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507698330}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > adjtimex({modes=ADJ_STATUS, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507792}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > adjtimex({modes=0, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=508000}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > adjtimex({modes=ADJ_FREQUENCY|ADJ_TICK, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=512146}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > adjtimex({modes=ADJ_MAXERROR|ADJ_ESTERROR|ADJ_STATUS, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=522506}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > adjtimex({modes=ADJ_SETOFFSET|ADJ_NANO, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616033, tv_usec=778717675}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > adjtimex({modes=ADJ_FREQUENCY|ADJ_TICK, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616033, tv_usec=784644657}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> >
> > (The struct timex fields above are from *after* the syscall was
> > executed, so they contain the current (new) values as set from the
> > kernel, except of the 'modes' field, which contains the original value
> > sent by the caller.)
> >
> > The changes to the time_maxerror, time_esterror, and time_constant
> > variables are not logged, as these are not important for security.
> >
> > Note that the records are emitted even when the actual value does not
> > change (i.e. when there is an explicit attempt to change a value, but
> > the new value equals the old one).
> >
> > An overview of changes that can be done via adjtimex(2) (based on
> > information from Miroslav Lichvar) and whether they are audited:
> > timekeeping_inject_offset() -- injects offset directly into system
> > time (AUDITED)
> > __timekeeping_set_tai_offset() -- sets the offset from the
> > International Atomic Time
> > (AUDITED)
> > NTP variables:
> > time_offset -- can adjust the clock by up to 0.5 seconds per call
> > and also speed it up or slow down by up to about
> > 0.05% (43 seconds per day) (AUDITED)
> > time_freq -- can speed up or slow down by up to about 0.05%
> > time_status -- can insert/delete leap seconds and it also enables/
> > disables synchronization of the hardware real-time
> > clock (AUDITED)
> > time_maxerror, time_esterror -- change error estimates used to
> > inform userspace applications
> > (NOT AUDITED)
> > time_constant -- controls the speed of the clock adjustments that
> > are made when time_offset is set (NOT AUDITED)
> > time_adjust -- can temporarily speed up or slow down the clock by up
> > to 0.05% (AUDITED)
> > tick_usec -- a more extreme version of time_freq; can speed up or
> > slow down the clock by up to 10% (AUDITED)
> >
> > Cc: Miroslav Lichvar <[email protected]>
> > Signed-off-by: Ondrej Mosnacek <[email protected]>
> > ---
> > kernel/time/ntp.c | 38 ++++++++++++++++++++++++++++++--------
> > kernel/time/timekeeping.c | 3 +++
> > 2 files changed, 33 insertions(+), 8 deletions(-)
> >
> > diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
> > index a09ded765f6c..f96c6d326aae 100644
> > --- a/kernel/time/ntp.c
> > +++ b/kernel/time/ntp.c
> > @@ -18,6 +18,7 @@
> > #include <linux/module.h>
> > #include <linux/rtc.h>
> > #include <linux/math64.h>
> > +#include <linux/audit.h>
> >
> > #include "ntp_internal.h"
> > #include "timekeeping_internal.h"
> > @@ -294,6 +295,8 @@ static inline s64 ntp_update_offset_fll(s64 offset64, long secs)
> >
> > static void ntp_update_offset(long offset)
> > {
> > + s64 old_offset = time_offset;
> > + s64 old_freq = time_freq;
> > s64 freq_adj;
> > s64 offset64;
> > long secs;
> > @@ -342,6 +345,9 @@ static void ntp_update_offset(long offset)
> > time_freq = max(freq_adj, -MAXFREQ_SCALED);
> >
> > time_offset = div_s64(offset64 << NTP_SCALE_SHIFT, NTP_INTERVAL_FREQ);
> > +
> > + audit_ntp_adjust("offset", old_offset, time_offset);
> > + audit_ntp_adjust("freq", old_freq, time_freq);
> > }
> >
> > /**
> > @@ -669,21 +675,31 @@ static inline void process_adjtimex_modes(struct timex *txc,
> > struct timespec64 *ts,
> > s32 *time_tai)
> > {
> > - if (txc->modes & ADJ_STATUS)
> > - process_adj_status(txc, ts);
> > + if (txc->modes & (ADJ_STATUS | ADJ_NANO | ADJ_MICRO)) {
> > + int old_status = time_status;
> > +
> > + if (txc->modes & ADJ_STATUS)
> > + process_adj_status(txc, ts);
> >
> > - if (txc->modes & ADJ_NANO)
> > - time_status |= STA_NANO;
> > + if (txc->modes & ADJ_NANO)
> > + time_status |= STA_NANO;
> >
> > - if (txc->modes & ADJ_MICRO)
> > - time_status &= ~STA_NANO;
> > + if (txc->modes & ADJ_MICRO)
> > + time_status &= ~STA_NANO;
> > +
> > + audit_ntp_adjust("status", old_status, time_status);
> > + }
> >
> > if (txc->modes & ADJ_FREQUENCY) {
> > + s64 old_freq = time_freq;
> > +
> > time_freq = txc->freq * PPM_SCALE;
> > time_freq = min(time_freq, MAXFREQ_SCALED);
> > time_freq = max(time_freq, -MAXFREQ_SCALED);
> > /* update pps_freq */
> > pps_set_freq(time_freq);
> > +
> > + audit_ntp_adjust("freq", old_freq, time_freq);
> > }
> >
> > if (txc->modes & ADJ_MAXERROR)
> > @@ -700,14 +716,18 @@ static inline void process_adjtimex_modes(struct timex *txc,
> > time_constant = max(time_constant, 0l);
> > }
> >
> > - if (txc->modes & ADJ_TAI && txc->constant > 0)
> > + if (txc->modes & ADJ_TAI && txc->constant > 0) {
> > + audit_ntp_adjust("tai", *time_tai, txc->constant);
> > *time_tai = txc->constant;
> > + }
>
> It appears this time_tai use of "constant" is different than
> time_constant, the former not mentioned by Miroslav Lichvar. What is it
> and is it important to log for security? It sounds like it is
> important.

I believe ADJ_TIMECONST and ADJ_TAI are completely different things
and just reuse the same struct field (I would guess that ADJ_TAI
support was added later and it was decided like this to keep the ABI).

The TAI offset is the offset of the clock from the International
Atomic Time, so basically the time zone offset. I suppose it can't
influence the audit timestamps, but changing timezones can still cause
all sorts of confusion throughout the system, so intuitively I would
say we should log it.

>
> > if (txc->modes & ADJ_OFFSET)
> > ntp_update_offset(txc->offset);
> >
> > - if (txc->modes & ADJ_TICK)
> > + if (txc->modes & ADJ_TICK) {
> > + audit_ntp_adjust("tick", tick_usec, txc->tick);
> > tick_usec = txc->tick;
> > + }
> >
> > if (txc->modes & (ADJ_TICK|ADJ_FREQUENCY|ADJ_OFFSET))
> > ntp_update_frequency();
> > @@ -729,6 +749,8 @@ int __do_adjtimex(struct timex *txc, struct timespec64 *ts, s32 *time_tai)
> > /* adjtime() is independent from ntp_adjtime() */
> > time_adjust = txc->offset;
> > ntp_update_frequency();
> > +
> > + audit_ntp_adjust("adjust", save_adjust, txc->offset);
> > }
> > txc->offset = save_adjust;
> > } else {
> > diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
> > index 4786df904c22..9089ac329e69 100644
> > --- a/kernel/time/timekeeping.c
> > +++ b/kernel/time/timekeeping.c
> > @@ -25,6 +25,7 @@
> > #include <linux/stop_machine.h>
> > #include <linux/pvclock_gtod.h>
> > #include <linux/compiler.h>
> > +#include <linux/audit.h>
> >
> > #include "tick-internal.h"
> > #include "ntp_internal.h"
> > @@ -2308,6 +2309,8 @@ int do_adjtimex(struct timex *txc)
> > ret = timekeeping_inject_offset(&delta);
> > if (ret)
> > return ret;
> > +
> > + audit_tk_injoffset(delta);
> > }
> >
> > getnstimeofday64(&ts);
> > --
> > 2.17.1
> >
> > --
> > Linux-audit mailing list
> > [email protected]
> > https://www.redhat.com/mailman/listinfo/linux-audit
>
> - RGB
>
> --
> Richard Guy Briggs <[email protected]>
> Sr. S/W Engineer, Kernel Security, Base Operating Systems
> Remote, Ottawa, Red Hat Canada
> IRC: rgb, SunRaycer
> Voice: +1.647.777.2635, Internal: (81) 32635

--
Ondrej Mosnacek <omosnace at redhat dot com>
Associate Software Engineer, Security Technologies
Red Hat, Inc.

2018-08-27 11:47:42

by Miroslav Lichvar

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 2/2] timekeeping/ntp: Audit clock/NTP params adjustments

On Mon, Aug 27, 2018 at 01:35:09PM +0200, Ondrej Mosnacek wrote:
> On Fri, Aug 24, 2018 at 9:51 PM Richard Guy Briggs <[email protected]> wrote:
> > It appears this time_tai use of "constant" is different than
> > time_constant, the former not mentioned by Miroslav Lichvar. What is it
> > and is it important to log for security? It sounds like it is
> > important.

> The TAI offset is the offset of the clock from the International
> Atomic Time, so basically the time zone offset. I suppose it can't
> influence the audit timestamps, but changing timezones can still cause
> all sorts of confusion throughout the system, so intuitively I would
> say we should log it.

It's not related to timezones. ADJ_TAI sets the offset of the system
TAI clock (CLOCK_TAI) relative to the standard UTC clock
(CLOCK_REALTIME). CLOCK_TAI is rarely used by applications. Setting
the TAI offset effectively injects a whole-second offset to the TAI
time.

--
Miroslav Lichvar

2018-08-27 12:03:38

by Ondrej Mosnacek

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 2/2] timekeeping/ntp: Audit clock/NTP params adjustments

On Mon, Aug 27, 2018 at 1:46 PM Miroslav Lichvar <[email protected]> wrote:
> On Mon, Aug 27, 2018 at 01:35:09PM +0200, Ondrej Mosnacek wrote:
> > On Fri, Aug 24, 2018 at 9:51 PM Richard Guy Briggs <[email protected]> wrote:
> > > It appears this time_tai use of "constant" is different than
> > > time_constant, the former not mentioned by Miroslav Lichvar. What is it
> > > and is it important to log for security? It sounds like it is
> > > important.
>
> > The TAI offset is the offset of the clock from the International
> > Atomic Time, so basically the time zone offset. I suppose it can't
> > influence the audit timestamps, but changing timezones can still cause
> > all sorts of confusion throughout the system, so intuitively I would
> > say we should log it.
>
> It's not related to timezones. ADJ_TAI sets the offset of the system
> TAI clock (CLOCK_TAI) relative to the standard UTC clock
> (CLOCK_REALTIME). CLOCK_TAI is rarely used by applications. Setting
> the TAI offset effectively injects a whole-second offset to the TAI
> time.

Ah, I stand corrected then. In that case I think we don't actually
need to audit it, since it just changes how the time reported by the
CLOCK_TAI clock type will be different from CLOCK_REALTIME. Playing
with this value could cause some strange jumps in time for
applications that use it, but this seems like a very unlikely
situation. It really depends on how careful we want to be (vs. how
eagerly we want to reduce unimportant records), but this already looks
like logging it would be overdoing it...

Thanks for the correction, I really should RTFM more :)

--
Ondrej Mosnacek <omosnace at redhat dot com>
Associate Software Engineer, Security Technologies
Red Hat, Inc.

2018-08-27 16:39:49

by Steve Grubb

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Monday, August 27, 2018 5:13:17 AM EDT Ondrej Mosnacek wrote:
> On Mon, Aug 27, 2018 at 9:50 AM Miroslav Lichvar <[email protected]>
wrote:
> > On Fri, Aug 24, 2018 at 02:00:00PM +0200, Ondrej Mosnacek wrote:
> > > This patch adds two auxiliary record types that will be used to
> > > annotate
> > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > been changed.
> >
> > It seems the "adjust" function intentionally logs also calls/modes
> > that don't actually change anything. Can you please explain it a bit
> > in the message?
> >
> > NTP/PTP daemons typically don't read the adjtimex values in a normal
> > operation and overwrite them on each update, even if they don't
> > change. If the audit function checked that oldval != newval, the
> > number of messages would be reduced and it might be easier to follow.
>
> We actually want to log any attempt to change a value, as even an
> intention to set/change something could be a hint that the process is
> trying to do something bad (see discussion at [1]).

One of the problems is that these applications can flood the logs very
quickly. An attempt to change is not needed unless it fails for permissions
reasons. So, limiting to actual changes is probably a good thing.

-Steve

> There are valid
> arguments both for and against this choice, but we have to pick one in
> the end... Anyway, I should explain the reasoning in the commit
> message better, right now it just states the fact without explanation
> (in the second patch), thank you for pointing my attention to it.
>
> [1] https://www.redhat.com/archives/linux-audit/2018-July/msg00061.html
>
> --
> Ondrej Mosnacek <omosnace at redhat dot com>
> Associate Software Engineer, Security Technologies
> Red Hat, Inc.





2018-08-27 21:44:13

by Thomas Gleixner

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 2/2] timekeeping/ntp: Audit clock/NTP params adjustments

On Mon, 27 Aug 2018, Miroslav Lichvar wrote:

> On Mon, Aug 27, 2018 at 01:35:09PM +0200, Ondrej Mosnacek wrote:
> > On Fri, Aug 24, 2018 at 9:51 PM Richard Guy Briggs <[email protected]> wrote:
> > > It appears this time_tai use of "constant" is different than
> > > time_constant, the former not mentioned by Miroslav Lichvar. What is it
> > > and is it important to log for security? It sounds like it is
> > > important.
>
> > The TAI offset is the offset of the clock from the International
> > Atomic Time, so basically the time zone offset. I suppose it can't
> > influence the audit timestamps, but changing timezones can still cause
> > all sorts of confusion throughout the system, so intuitively I would
> > say we should log it.
>
> It's not related to timezones. ADJ_TAI sets the offset of the system
> TAI clock (CLOCK_TAI) relative to the standard UTC clock
> (CLOCK_REALTIME). CLOCK_TAI is rarely used by applications. Setting
> the TAI offset effectively injects a whole-second offset to the TAI
> time.

Rarely used today, but with TSN it's going to get more usage in not so
distant future.

Thanks,

tglx

2018-09-13 13:59:51

by Ondrej Mosnacek

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Mon, Aug 27, 2018 at 6:38 PM Steve Grubb <[email protected]> wrote:
> On Monday, August 27, 2018 5:13:17 AM EDT Ondrej Mosnacek wrote:
> > On Mon, Aug 27, 2018 at 9:50 AM Miroslav Lichvar <[email protected]>
> wrote:
> > > On Fri, Aug 24, 2018 at 02:00:00PM +0200, Ondrej Mosnacek wrote:
> > > > This patch adds two auxiliary record types that will be used to
> > > > annotate
> > > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > > been changed.
> > >
> > > It seems the "adjust" function intentionally logs also calls/modes
> > > that don't actually change anything. Can you please explain it a bit
> > > in the message?
> > >
> > > NTP/PTP daemons typically don't read the adjtimex values in a normal
> > > operation and overwrite them on each update, even if they don't
> > > change. If the audit function checked that oldval != newval, the
> > > number of messages would be reduced and it might be easier to follow.
> >
> > We actually want to log any attempt to change a value, as even an
> > intention to set/change something could be a hint that the process is
> > trying to do something bad (see discussion at [1]).
>
> One of the problems is that these applications can flood the logs very
> quickly. An attempt to change is not needed unless it fails for permissions
> reasons. So, limiting to actual changes is probably a good thing.

Well, Richard seemed to "violently" agree with the opposite, so now I
don't know which way to go... Paul, you are the official tie-breaker
here, which do you prefer?

>
> -Steve
>
> > There are valid
> > arguments both for and against this choice, but we have to pick one in
> > the end... Anyway, I should explain the reasoning in the commit
> > message better, right now it just states the fact without explanation
> > (in the second patch), thank you for pointing my attention to it.
> >
> > [1] https://www.redhat.com/archives/linux-audit/2018-July/msg00061.html
> >
> > --
> > Ondrej Mosnacek <omosnace at redhat dot com>
> > Associate Software Engineer, Security Technologies
> > Red Hat, Inc.
>
>
>
>

--
Ondrej Mosnacek <omosnace at redhat dot com>
Associate Software Engineer, Security Technologies
Red Hat, Inc.

2018-09-13 15:20:12

by Richard Guy Briggs

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On 2018-09-13 15:59, Ondrej Mosnacek wrote:
> On Mon, Aug 27, 2018 at 6:38 PM Steve Grubb <[email protected]> wrote:
> > On Monday, August 27, 2018 5:13:17 AM EDT Ondrej Mosnacek wrote:
> > > On Mon, Aug 27, 2018 at 9:50 AM Miroslav Lichvar <[email protected]>
> > wrote:
> > > > On Fri, Aug 24, 2018 at 02:00:00PM +0200, Ondrej Mosnacek wrote:
> > > > > This patch adds two auxiliary record types that will be used to
> > > > > annotate
> > > > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > > > been changed.
> > > >
> > > > It seems the "adjust" function intentionally logs also calls/modes
> > > > that don't actually change anything. Can you please explain it a bit
> > > > in the message?
> > > >
> > > > NTP/PTP daemons typically don't read the adjtimex values in a normal
> > > > operation and overwrite them on each update, even if they don't
> > > > change. If the audit function checked that oldval != newval, the
> > > > number of messages would be reduced and it might be easier to follow.
> > >
> > > We actually want to log any attempt to change a value, as even an
> > > intention to set/change something could be a hint that the process is
> > > trying to do something bad (see discussion at [1]).
> >
> > One of the problems is that these applications can flood the logs very
> > quickly. An attempt to change is not needed unless it fails for permissions
> > reasons. So, limiting to actual changes is probably a good thing.
>
> Well, Richard seemed to "violently" agree with the opposite, so now I
> don't know which way to go... Paul, you are the official tie-breaker
> here, which do you prefer?

The circumstances have changed with new information being added. I
recall violently agreeing several iterations ago with your previous
assessment, which has also changed with this new information. I'd agree
with Steve that a flood of information about something that did not
change value could hide important information.

(BTW: The expression "to violoently agree with" is generally used in a
situation where two parties appear to have been arguing two different
sides of an issue and then realize they have much more in common than
initially apparent.)

> > -Steve
> >
> > > There are valid
> > > arguments both for and against this choice, but we have to pick one in
> > > the end... Anyway, I should explain the reasoning in the commit
> > > message better, right now it just states the fact without explanation
> > > (in the second patch), thank you for pointing my attention to it.
> > >
> > > [1] https://www.redhat.com/archives/linux-audit/2018-July/msg00061.html
> > >
> > > --
> > > Ondrej Mosnacek <omosnace at redhat dot com>
>
> Ondrej Mosnacek <omosnace at redhat dot com>

- RGB

--
Richard Guy Briggs <[email protected]>
Sr. S/W Engineer, Kernel Security, Base Operating Systems
Remote, Ottawa, Red Hat Canada
IRC: rgb, SunRaycer
Voice: +1.647.777.2635, Internal: (81) 32635

2018-09-13 15:42:17

by Richard Guy Briggs

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 2/2] timekeeping/ntp: Audit clock/NTP params adjustments

On 2018-08-27 13:35, Ondrej Mosnacek wrote:
> On Fri, Aug 24, 2018 at 9:51 PM Richard Guy Briggs <[email protected]> wrote:
> > On 2018-08-24 14:00, Ondrej Mosnacek wrote:
> > > This patch adds logging of all attempts to either inject an offset into
> > > the clock (producing an AUDIT_TIME_INJOFFSET record) or adjust an NTP
> > > parameter (producing an AUDIT_TIME_ADJNTPVAL record).
> >
> > I thought I saw it suggested earlier in one of the replies to a previous
> > revision of the patchset to separate the two types of records with their
> > calling circumstances. The inj-offset bits could stand alone in their
> > own patch leaving all the rest in its own patch. The record numbers and
> > examples are easier to offer when given together, but they aren't as
> > clear they are indepnendent records and callers. That way, each patch
> > stands on its own. (more below)
>
> Well, the idea of current split-up is to separate changes in different
> subsystems. I would argue that the two record types are related enough
> (and the diffs short enough) that it is worth keeping them together.

I would group the introduction of the macro with its usage, not
splitting across sub-systems. If you feel that the two are similar
enough, then all this should be in one patch. The record code patch
depends on the record macro, so they should be in the same patch. The
two records don't depend on each other and could be in seperate patches
with one cover letter to introduce and tie them together.

> > > For reference, running the following commands:
> > >
> > > auditctl -D
> > > auditctl -a exit,always -F arch=b64 -S adjtimex
> > > chronyd -q
> > >
> > > produces audit records like this:
> > >
> > > type=TIME_ADJNTPVAL msg=audit(1530616044.507:5): op=adjust old=0 new=0
> > > type=SYSCALL msg=audit(1530616044.507:5): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=0 a2=4 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > > type=PROCTITLE msg=audit(1530616044.507:5): proctitle=6368726F6E7964002D71
> > > type=SYSCALL msg=audit(1530616044.507:6): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=1 a2=1 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > > type=PROCTITLE msg=audit(1530616044.507:6): proctitle=6368726F6E7964002D71
> > > type=TIME_INJOFFSET msg=audit(1530616044.507:7): sec=0 nsec=0
> > > type=TIME_ADJNTPVAL msg=audit(1530616044.507:7): op=status old=64 new=8256
> > > type=SYSCALL msg=audit(1530616044.507:7): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78c00 a1=1 a2=1 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > > type=PROCTITLE msg=audit(1530616044.507:7): proctitle=6368726F6E7964002D71
> > > type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=status old=8256 new=8257
> > > type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=offset old=0 new=0
> > > type=TIME_ADJNTPVAL msg=audit(1530616044.507:8): op=freq old=0 new=0
> > > type=SYSCALL msg=audit(1530616044.507:8): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ab0 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > > type=PROCTITLE msg=audit(1530616044.507:8): proctitle=6368726F6E7964002D71
> > > type=TIME_ADJNTPVAL msg=audit(1530616044.507:9): op=status old=8257 new=64
> > > type=SYSCALL msg=audit(1530616044.507:9): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ab0 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > > type=PROCTITLE msg=audit(1530616044.507:9): proctitle=6368726F6E7964002D71
> > > type=SYSCALL msg=audit(1530616044.507:10): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78a70 a1=0 a2=55e129c850c0 a3=7f754ae28c0a items=0 ppid=626 pid=629 auid=0 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > > type=PROCTITLE msg=audit(1530616044.507:10): proctitle=6368726F6E7964002D71
> > > type=TIME_ADJNTPVAL msg=audit(1530616044.511:11): op=freq old=0 new=49180377088000
> > > type=TIME_ADJNTPVAL msg=audit(1530616044.511:11): op=tick old=10000 new=10000
> > > type=SYSCALL msg=audit(1530616044.511:11): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78ad0 a1=0 a2=2710 a3=f42f82a800000 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > > type=PROCTITLE msg=audit(1530616044.511:11): proctitle=6368726F6E7964002D71
> > > type=TIME_ADJNTPVAL msg=audit(1530616044.521:12): op=status old=64 new=64
> > > type=SYSCALL msg=audit(1530616044.521:12): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78b40 a1=1 a2=40 a3=f91f6ef84fbab items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > > type=PROCTITLE msg=audit(1530616044.521:12): proctitle=6368726F6E7964002D71
> > > type=TIME_INJOFFSET msg=audit(1530616049.652:13): sec=-16 nsec=124887145
> > > type=TIME_ADJNTPVAL msg=audit(1530616049.652:13): op=status old=64 new=8256
> > > type=SYSCALL msg=audit(1530616049.652:13): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78270 a1=1 a2=fffffffffffffff0 a3=137b828205ca12 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > > type=PROCTITLE msg=audit(1530616049.652:13): proctitle=6368726F6E7964002D71
> > > type=TIME_ADJNTPVAL msg=audit(1530616033.783:14): op=freq old=49180377088000 new=49180377088000
> > > type=TIME_ADJNTPVAL msg=audit(1530616033.783:14): op=tick old=10000 new=10000
> > > type=SYSCALL msg=audit(1530616033.783:14): arch=c000003e syscall=159 success=yes exit=5 a0=7fff57e78bc0 a1=0 a2=2710 a3=0 items=0 ppid=626 pid=629 auid=0 uid=385 gid=382 euid=385 suid=385 fsuid=385 egid=382 sgid=382 fsgid=382 tty=(none) ses=1 comm="chronyd" exe="/usr/sbin/chronyd" subj=system_u:system_r:kernel_t:s0 key=(null)
> > > type=PROCTITLE msg=audit(1530616033.783:14): proctitle=6368726F6E7964002D71
> > >
> > > The chronyd command that produced the above records executed the
> > > following adjtimex(2) syscalls (as per strace output):
> > >
> > > adjtimex({modes=ADJ_OFFSET|0x8000, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507215}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > > adjtimex({modes=ADJ_MAXERROR, offset=0, freq=0, maxerror=0, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507438}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > > adjtimex({modes=ADJ_SETOFFSET|ADJ_NANO, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507604737}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > > adjtimex({modes=ADJ_OFFSET|ADJ_STATUS, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_PLL|STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507698330}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > > adjtimex({modes=ADJ_STATUS, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=507792}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > > adjtimex({modes=0, offset=0, freq=0, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=508000}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > > adjtimex({modes=ADJ_FREQUENCY|ADJ_TICK, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=512146}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > > adjtimex({modes=ADJ_MAXERROR|ADJ_ESTERROR|ADJ_STATUS, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616044, tv_usec=522506}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > > adjtimex({modes=ADJ_SETOFFSET|ADJ_NANO, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616033, tv_usec=778717675}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > > adjtimex({modes=ADJ_FREQUENCY|ADJ_TICK, offset=0, freq=750433, maxerror=16000000, esterror=16000000, status=STA_UNSYNC|STA_NANO, constant=2, precision=1, tolerance=32768000, time={tv_sec=1530616033, tv_usec=784644657}, tick=10000, ppsfreq=0, jitter=0, shift=0, stabil=0, jitcnt=0, calcnt=0, errcnt=0, stbcnt=0, tai=0}) = 5 (TIME_ERROR)
> > >
> > > (The struct timex fields above are from *after* the syscall was
> > > executed, so they contain the current (new) values as set from the
> > > kernel, except of the 'modes' field, which contains the original value
> > > sent by the caller.)
> > >
> > > The changes to the time_maxerror, time_esterror, and time_constant
> > > variables are not logged, as these are not important for security.
> > >
> > > Note that the records are emitted even when the actual value does not
> > > change (i.e. when there is an explicit attempt to change a value, but
> > > the new value equals the old one).
> > >
> > > An overview of changes that can be done via adjtimex(2) (based on
> > > information from Miroslav Lichvar) and whether they are audited:
> > > timekeeping_inject_offset() -- injects offset directly into system
> > > time (AUDITED)
> > > __timekeeping_set_tai_offset() -- sets the offset from the
> > > International Atomic Time
> > > (AUDITED)
> > > NTP variables:
> > > time_offset -- can adjust the clock by up to 0.5 seconds per call
> > > and also speed it up or slow down by up to about
> > > 0.05% (43 seconds per day) (AUDITED)
> > > time_freq -- can speed up or slow down by up to about 0.05%
> > > time_status -- can insert/delete leap seconds and it also enables/
> > > disables synchronization of the hardware real-time
> > > clock (AUDITED)
> > > time_maxerror, time_esterror -- change error estimates used to
> > > inform userspace applications
> > > (NOT AUDITED)
> > > time_constant -- controls the speed of the clock adjustments that
> > > are made when time_offset is set (NOT AUDITED)
> > > time_adjust -- can temporarily speed up or slow down the clock by up
> > > to 0.05% (AUDITED)
> > > tick_usec -- a more extreme version of time_freq; can speed up or
> > > slow down the clock by up to 10% (AUDITED)
> > >
> > > Cc: Miroslav Lichvar <[email protected]>
> > > Signed-off-by: Ondrej Mosnacek <[email protected]>
> > > ---
> > > kernel/time/ntp.c | 38 ++++++++++++++++++++++++++++++--------
> > > kernel/time/timekeeping.c | 3 +++
> > > 2 files changed, 33 insertions(+), 8 deletions(-)
> > >
> > > diff --git a/kernel/time/ntp.c b/kernel/time/ntp.c
> > > index a09ded765f6c..f96c6d326aae 100644
> > > --- a/kernel/time/ntp.c
> > > +++ b/kernel/time/ntp.c
> > > @@ -18,6 +18,7 @@
> > > #include <linux/module.h>
> > > #include <linux/rtc.h>
> > > #include <linux/math64.h>
> > > +#include <linux/audit.h>
> > >
> > > #include "ntp_internal.h"
> > > #include "timekeeping_internal.h"
> > > @@ -294,6 +295,8 @@ static inline s64 ntp_update_offset_fll(s64 offset64, long secs)
> > >
> > > static void ntp_update_offset(long offset)
> > > {
> > > + s64 old_offset = time_offset;
> > > + s64 old_freq = time_freq;
> > > s64 freq_adj;
> > > s64 offset64;
> > > long secs;
> > > @@ -342,6 +345,9 @@ static void ntp_update_offset(long offset)
> > > time_freq = max(freq_adj, -MAXFREQ_SCALED);
> > >
> > > time_offset = div_s64(offset64 << NTP_SCALE_SHIFT, NTP_INTERVAL_FREQ);
> > > +
> > > + audit_ntp_adjust("offset", old_offset, time_offset);
> > > + audit_ntp_adjust("freq", old_freq, time_freq);
> > > }
> > >
> > > /**
> > > @@ -669,21 +675,31 @@ static inline void process_adjtimex_modes(struct timex *txc,
> > > struct timespec64 *ts,
> > > s32 *time_tai)
> > > {
> > > - if (txc->modes & ADJ_STATUS)
> > > - process_adj_status(txc, ts);
> > > + if (txc->modes & (ADJ_STATUS | ADJ_NANO | ADJ_MICRO)) {
> > > + int old_status = time_status;
> > > +
> > > + if (txc->modes & ADJ_STATUS)
> > > + process_adj_status(txc, ts);
> > >
> > > - if (txc->modes & ADJ_NANO)
> > > - time_status |= STA_NANO;
> > > + if (txc->modes & ADJ_NANO)
> > > + time_status |= STA_NANO;
> > >
> > > - if (txc->modes & ADJ_MICRO)
> > > - time_status &= ~STA_NANO;
> > > + if (txc->modes & ADJ_MICRO)
> > > + time_status &= ~STA_NANO;
> > > +
> > > + audit_ntp_adjust("status", old_status, time_status);
> > > + }
> > >
> > > if (txc->modes & ADJ_FREQUENCY) {
> > > + s64 old_freq = time_freq;
> > > +
> > > time_freq = txc->freq * PPM_SCALE;
> > > time_freq = min(time_freq, MAXFREQ_SCALED);
> > > time_freq = max(time_freq, -MAXFREQ_SCALED);
> > > /* update pps_freq */
> > > pps_set_freq(time_freq);
> > > +
> > > + audit_ntp_adjust("freq", old_freq, time_freq);
> > > }
> > >
> > > if (txc->modes & ADJ_MAXERROR)
> > > @@ -700,14 +716,18 @@ static inline void process_adjtimex_modes(struct timex *txc,
> > > time_constant = max(time_constant, 0l);
> > > }
> > >
> > > - if (txc->modes & ADJ_TAI && txc->constant > 0)
> > > + if (txc->modes & ADJ_TAI && txc->constant > 0) {
> > > + audit_ntp_adjust("tai", *time_tai, txc->constant);
> > > *time_tai = txc->constant;
> > > + }
> >
> > It appears this time_tai use of "constant" is different than
> > time_constant, the former not mentioned by Miroslav Lichvar. What is it
> > and is it important to log for security? It sounds like it is
> > important.
>
> I believe ADJ_TIMECONST and ADJ_TAI are completely different things
> and just reuse the same struct field (I would guess that ADJ_TAI
> support was added later and it was decided like this to keep the ABI).
>
> The TAI offset is the offset of the clock from the International
> Atomic Time, so basically the time zone offset. I suppose it can't
> influence the audit timestamps, but changing timezones can still cause
> all sorts of confusion throughout the system, so intuitively I would
> say we should log it.
>
> >
> > > if (txc->modes & ADJ_OFFSET)
> > > ntp_update_offset(txc->offset);
> > >
> > > - if (txc->modes & ADJ_TICK)
> > > + if (txc->modes & ADJ_TICK) {
> > > + audit_ntp_adjust("tick", tick_usec, txc->tick);
> > > tick_usec = txc->tick;
> > > + }
> > >
> > > if (txc->modes & (ADJ_TICK|ADJ_FREQUENCY|ADJ_OFFSET))
> > > ntp_update_frequency();
> > > @@ -729,6 +749,8 @@ int __do_adjtimex(struct timex *txc, struct timespec64 *ts, s32 *time_tai)
> > > /* adjtime() is independent from ntp_adjtime() */
> > > time_adjust = txc->offset;
> > > ntp_update_frequency();
> > > +
> > > + audit_ntp_adjust("adjust", save_adjust, txc->offset);
> > > }
> > > txc->offset = save_adjust;
> > > } else {
> > > diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
> > > index 4786df904c22..9089ac329e69 100644
> > > --- a/kernel/time/timekeeping.c
> > > +++ b/kernel/time/timekeeping.c
> > > @@ -25,6 +25,7 @@
> > > #include <linux/stop_machine.h>
> > > #include <linux/pvclock_gtod.h>
> > > #include <linux/compiler.h>
> > > +#include <linux/audit.h>
> > >
> > > #include "tick-internal.h"
> > > #include "ntp_internal.h"
> > > @@ -2308,6 +2309,8 @@ int do_adjtimex(struct timex *txc)
> > > ret = timekeeping_inject_offset(&delta);
> > > if (ret)
> > > return ret;
> > > +
> > > + audit_tk_injoffset(delta);
> > > }
> > >
> > > getnstimeofday64(&ts);
> > > --
> > > 2.17.1
> > >
> > > --
> > > Linux-audit mailing list
> > > [email protected]
> > > https://www.redhat.com/mailman/listinfo/linux-audit
> >
> > - RGB
> >
> > --
> > Richard Guy Briggs <[email protected]>
> > Sr. S/W Engineer, Kernel Security, Base Operating Systems
> > Remote, Ottawa, Red Hat Canada
> > IRC: rgb, SunRaycer
> > Voice: +1.647.777.2635, Internal: (81) 32635
>
> --
> Ondrej Mosnacek <omosnace at redhat dot com>
> Associate Software Engineer, Security Technologies
> Red Hat, Inc.

- RGB

--
Richard Guy Briggs <[email protected]>
Sr. S/W Engineer, Kernel Security, Base Operating Systems
Remote, Ottawa, Red Hat Canada
IRC: rgb, SunRaycer
Voice: +1.647.777.2635, Internal: (81) 32635

2018-09-13 16:00:28

by Richard Guy Briggs

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On 2018-08-27 10:28, Ondrej Mosnacek wrote:
> On Fri, Aug 24, 2018 at 8:33 PM John Stultz <[email protected]> wrote:
> > On Fri, Aug 24, 2018 at 5:00 AM, Ondrej Mosnacek <[email protected]> wrote:
> > > This patch adds two auxiliary record types that will be used to annotate
> > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > been changed.
> > >
> > > Next, it adds two functions to the audit interface:
> > > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > > offset is injected by a syscall from userspace,
> > > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > > variable is changed by a syscall from userspace.
> > >
> > > Quick reference for the fields of the new records:
> > > AUDIT_TIME_INJOFFSET
> > > sec - the 'seconds' part of the offset
> > > nsec - the 'nanoseconds' part of the offset
> > > AUDIT_TIME_ADJNTPVAL
> > > op - which value was adjusted:
> > > offset - corresponding to the time_offset variable
> > > freq - corresponding to the time_freq variable
> > > status - corresponding to the time_status variable
> > > adjust - corresponding to the time_adjust variable
> > > tick - corresponding to the tick_usec variable
> > > tai - corresponding to the timekeeping's TAI offset
> > > old - the old value
> > > new - the new value
> > >
> > > Signed-off-by: Ondrej Mosnacek <[email protected]>
> > > ---
> > > include/linux/audit.h | 21 +++++++++++++++++++++
> > > include/uapi/linux/audit.h | 2 ++
> > > kernel/auditsc.c | 15 +++++++++++++++
> > > 3 files changed, 38 insertions(+)
> > >
> > > diff --git a/include/linux/audit.h b/include/linux/audit.h
> > > index 9334fbef7bae..0d084d4b4042 100644
> > > --- a/include/linux/audit.h
> > > +++ b/include/linux/audit.h
> > > @@ -26,6 +26,7 @@
> > > #include <linux/sched.h>
> > > #include <linux/ptrace.h>
> > > #include <uapi/linux/audit.h>
> > > +#include <uapi/linux/timex.h>
> > >
> > > #define AUDIT_INO_UNSET ((unsigned long)-1)
> > > #define AUDIT_DEV_UNSET ((dev_t)-1)
> > > @@ -356,6 +357,8 @@ extern void __audit_log_capset(const struct cred *new, const struct cred *old);
> > > extern void __audit_mmap_fd(int fd, int flags);
> > > extern void __audit_log_kern_module(char *name);
> > > extern void __audit_fanotify(unsigned int response);
> > > +extern void __audit_tk_injoffset(struct timespec64 offset);
> > > +extern void __audit_ntp_adjust(const char *type, s64 oldval, s64 newval);
> > >
> > > static inline void audit_ipc_obj(struct kern_ipc_perm *ipcp)
> > > {
> > > @@ -458,6 +461,18 @@ static inline void audit_fanotify(unsigned int response)
> > > __audit_fanotify(response);
> > > }
> > >
> > > +static inline void audit_tk_injoffset(struct timespec64 offset)
> > > +{
> > > + if (!audit_dummy_context())
> > > + __audit_tk_injoffset(offset);
> > > +}
> > > +
> > > +static inline void audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> > > +{
> > > + if (!audit_dummy_context())
> > > + __audit_ntp_adjust(type, oldval, newval);
> > > +}
> > > +
> > > extern int audit_n_rules;
> > > extern int audit_signals;
> > > #else /* CONFIG_AUDITSYSCALL */
> > > @@ -584,6 +599,12 @@ static inline void audit_log_kern_module(char *name)
> > > static inline void audit_fanotify(unsigned int response)
> > > { }
> > >
> > > +static inline void audit_tk_injoffset(struct timespec64 offset)
> > > +{ }
> > > +
> > > +static inline void audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> > > +{ }
> > > +
> > > static inline void audit_ptrace(struct task_struct *t)
> > > { }
> > > #define audit_n_rules 0
> > > diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> > > index 4e3eaba84175..242ce562b41a 100644
> > > --- a/include/uapi/linux/audit.h
> > > +++ b/include/uapi/linux/audit.h
> > > @@ -114,6 +114,8 @@
> > > #define AUDIT_REPLACE 1329 /* Replace auditd if this packet unanswerd */
> > > #define AUDIT_KERN_MODULE 1330 /* Kernel Module events */
> > > #define AUDIT_FANOTIFY 1331 /* Fanotify access decision */
> > > +#define AUDIT_TIME_INJOFFSET 1332 /* Timekeeping offset injected */
> > > +#define AUDIT_TIME_ADJNTPVAL 1333 /* NTP value adjustment */
> > >
> > > #define AUDIT_AVC 1400 /* SE Linux avc denial or grant */
> > > #define AUDIT_SELINUX_ERR 1401 /* Internal SE Linux Errors */
> > > diff --git a/kernel/auditsc.c b/kernel/auditsc.c
> > > index fb207466e99b..d355d32d9765 100644
> > > --- a/kernel/auditsc.c
> > > +++ b/kernel/auditsc.c
> > > @@ -2422,6 +2422,21 @@ void __audit_fanotify(unsigned int response)
> > > AUDIT_FANOTIFY, "resp=%u", response);
> > > }
> > >
> > > +/* We need to allocate with GFP_ATOMIC here, since these two functions will be
> > > + * called while holding the timekeeping lock: */
> > > +void __audit_tk_injoffset(struct timespec64 offset)
> > > +{
> > > + audit_log(audit_context(), GFP_ATOMIC, AUDIT_TIME_INJOFFSET,
> > > + "sec=%lli nsec=%li", (long long)offset.tv_sec, offset.tv_nsec);
> > > +}
> > > +
> > > +void __audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> > > +{
> > > + audit_log(audit_context(), GFP_ATOMIC, AUDIT_TIME_ADJNTPVAL,
> > > + "op=%s old=%lli new=%lli", type,
> > > + (long long)oldval, (long long)newval);
> > > +}
> >
> > So it looks like you've been careful here, but just want to make sure,
> > nothing in the audit_log path calls anything that might possibly call
> > back into timekeeping code? We've had a number of issues over time
> > where debug calls out to other subsystems end up getting tweaked to
> > add some timestamping and we deadlock. :)
>
> Hm, that's a good point... AFAIK, the only place where audit_log()
> might call into timekeeping is when it captures the timestamp for the
> record (via ktime_get_coarse_real_ts64()), but this is only called
> when the functions are called outside of a syscall and that should
> never happen here. I'm thinking I could harden this more by returning
> early from these functions if WARN_ON_ONCE(!audit_context() ||
> !audit_context()->in_syscall)... It is not a perfect solution, but at
> least there will be something in the code reminding us about this
> issue.

It looks like this should be safe already since if there is no context,
it won't call into the real audit logging function and as you point out,
this should never happen outside of a syscall.

> > One approach we've done to make sure we don't create a trap for future
> > changes in other subsystems, is avoid calling into other subsystems
> > until later when we've dropped the locks, (see clock_was_set). Its a
> > little rough for some of the things done deep in the ntp code, but
> > might be an extra cautious approach to try.
>
> I'm afraid that delaying the audit_* calls would complicate things too
> much here... Paul/Richard, any thoughts?

The other way to address this would be to have your timekeeping audit
logging functions save the parameters you want to log somewhere in the
struct audit_context union and have it print the record on syscall exit
based on the presence of this type and applicable filters.

> Ondrej Mosnacek <omosnace at redhat dot com>

- RGB

--
Richard Guy Briggs <[email protected]>
Sr. S/W Engineer, Kernel Security, Base Operating Systems
Remote, Ottawa, Red Hat Canada
IRC: rgb, SunRaycer
Voice: +1.647.777.2635, Internal: (81) 32635

2018-09-14 03:11:24

by Paul Moore

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Thu, Sep 13, 2018 at 9:59 AM Ondrej Mosnacek <[email protected]> wrote:
> On Mon, Aug 27, 2018 at 6:38 PM Steve Grubb <[email protected]> wrote:
> > On Monday, August 27, 2018 5:13:17 AM EDT Ondrej Mosnacek wrote:
> > > On Mon, Aug 27, 2018 at 9:50 AM Miroslav Lichvar <[email protected]>
> > wrote:
> > > > On Fri, Aug 24, 2018 at 02:00:00PM +0200, Ondrej Mosnacek wrote:
> > > > > This patch adds two auxiliary record types that will be used to
> > > > > annotate
> > > > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > > > been changed.
> > > >
> > > > It seems the "adjust" function intentionally logs also calls/modes
> > > > that don't actually change anything. Can you please explain it a bit
> > > > in the message?
> > > >
> > > > NTP/PTP daemons typically don't read the adjtimex values in a normal
> > > > operation and overwrite them on each update, even if they don't
> > > > change. If the audit function checked that oldval != newval, the
> > > > number of messages would be reduced and it might be easier to follow.
> > >
> > > We actually want to log any attempt to change a value, as even an
> > > intention to set/change something could be a hint that the process is
> > > trying to do something bad (see discussion at [1]).
> >
> > One of the problems is that these applications can flood the logs very
> > quickly. An attempt to change is not needed unless it fails for permissions
> > reasons. So, limiting to actual changes is probably a good thing.
>
> Well, Richard seemed to "violently" agree with the opposite, so now I
> don't know which way to go... Paul, you are the official tie-breaker
> here, which do you prefer?

The general idea is that we only care about *changes* to the system
state, so if a process is setting a variable to with a value that
matches it's current value I see no reason why we need to generate a
change record.

Another thing to keep in mind, we can always change the behavior to be
more verbose (*always* generate a record, regardless of value) without
likely causing a regression, but limiting records is more difficult
and more likely to cause regressions.

--
paul moore
http://www.paul-moore.com

2018-09-14 03:30:46

by Paul Moore

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Fri, Aug 24, 2018 at 8:00 AM Ondrej Mosnacek <[email protected]> wrote:
> This patch adds two auxiliary record types that will be used to annotate
> the adjtimex SYSCALL records with the NTP/timekeeping values that have
> been changed.
>
> Next, it adds two functions to the audit interface:
> - audit_tk_injoffset(), which will be called whenever a timekeeping
> offset is injected by a syscall from userspace,
> - audit_ntp_adjust(), which will be called whenever an NTP internal
> variable is changed by a syscall from userspace.
>
> Quick reference for the fields of the new records:
> AUDIT_TIME_INJOFFSET
> sec - the 'seconds' part of the offset
> nsec - the 'nanoseconds' part of the offset
> AUDIT_TIME_ADJNTPVAL
> op - which value was adjusted:
> offset - corresponding to the time_offset variable
> freq - corresponding to the time_freq variable
> status - corresponding to the time_status variable
> adjust - corresponding to the time_adjust variable
> tick - corresponding to the tick_usec variable
> tai - corresponding to the timekeeping's TAI offset

I understand that reusing "op" is tempting, but the above aren't
really operations, they are state variables which are being changed.
Using the CONFIG_CHANGE record as a basis, I wonder if we are better
off with something like the following:

type=TIME_CHANGE <var>=<value_new> old=<value_old>

... you might need to preface the variable names with something like
"ntp_" or "offset_". You'll notice I'm also suggesting we use a
single record type here; is there any reason why two records types are
required?

> old - the old value
> new - the new value
>
> Signed-off-by: Ondrej Mosnacek <[email protected]>
> ---
> include/linux/audit.h | 21 +++++++++++++++++++++
> include/uapi/linux/audit.h | 2 ++
> kernel/auditsc.c | 15 +++++++++++++++
> 3 files changed, 38 insertions(+)

A reminder that we need tests for these new records and a RFE page on the wiki:

* https://github.com/linux-audit/audit-testsuite
* https://github.com/linux-audit/audit-kernel/wiki

--
paul moore
http://www.paul-moore.com

2018-09-14 15:23:17

by Richard Guy Briggs

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On 2018-09-13 23:18, Paul Moore wrote:
> On Fri, Aug 24, 2018 at 8:00 AM Ondrej Mosnacek <[email protected]> wrote:
> > This patch adds two auxiliary record types that will be used to annotate
> > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > been changed.
> >
> > Next, it adds two functions to the audit interface:
> > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > offset is injected by a syscall from userspace,
> > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > variable is changed by a syscall from userspace.
> >
> > Quick reference for the fields of the new records:
> > AUDIT_TIME_INJOFFSET
> > sec - the 'seconds' part of the offset
> > nsec - the 'nanoseconds' part of the offset
> > AUDIT_TIME_ADJNTPVAL
> > op - which value was adjusted:
> > offset - corresponding to the time_offset variable
> > freq - corresponding to the time_freq variable
> > status - corresponding to the time_status variable
> > adjust - corresponding to the time_adjust variable
> > tick - corresponding to the tick_usec variable
> > tai - corresponding to the timekeeping's TAI offset
>
> I understand that reusing "op" is tempting, but the above aren't
> really operations, they are state variables which are being changed.
> Using the CONFIG_CHANGE record as a basis, I wonder if we are better
> off with something like the following:
>
> type=TIME_CHANGE <var>=<value_new> old=<value_old>
>
> ... you might need to preface the variable names with something like
> "ntp_" or "offset_". You'll notice I'm also suggesting we use a
> single record type here; is there any reason why two records types are
> required?

Why not do something like:

type=TIME_CHANGE var=<var> new=<value_new> old=<value_old>

So that we don't pollute the field namespace *and* create 8 variants on
the same record format? This shouldn't be much of a concern with binary
record formats, but we're stuck with the current parsing scheme for now.

> > old - the old value
> > new - the new value
> >
> > Signed-off-by: Ondrej Mosnacek <[email protected]>
> > ---
> > include/linux/audit.h | 21 +++++++++++++++++++++
> > include/uapi/linux/audit.h | 2 ++
> > kernel/auditsc.c | 15 +++++++++++++++
> > 3 files changed, 38 insertions(+)
>
> A reminder that we need tests for these new records and a RFE page on the wiki:
>
> * https://github.com/linux-audit/audit-testsuite
> * https://github.com/linux-audit/audit-kernel/wiki
>
> --
> paul moore
> http://www.paul-moore.com

- RGB

--
Richard Guy Briggs <[email protected]>
Sr. S/W Engineer, Kernel Security, Base Operating Systems
Remote, Ottawa, Red Hat Canada
IRC: rgb, SunRaycer
Voice: +1.647.777.2635, Internal: (81) 32635

2018-09-14 15:34:31

by Steve Grubb

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Friday, September 14, 2018 11:16:43 AM EDT Richard Guy Briggs wrote:
> On 2018-09-13 23:18, Paul Moore wrote:
> > On Fri, Aug 24, 2018 at 8:00 AM Ondrej Mosnacek <[email protected]>
wrote:
> > > This patch adds two auxiliary record types that will be used to
> > > annotate
> > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > been changed.
> > >
> > > Next, it adds two functions to the audit interface:
> > > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > >
> > > offset is injected by a syscall from userspace,
> > >
> > > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > >
> > > variable is changed by a syscall from userspace.
> > >
> > > Quick reference for the fields of the new records:
> > > AUDIT_TIME_INJOFFSET
> > >
> > > sec - the 'seconds' part of the offset
> > > nsec - the 'nanoseconds' part of the offset
> > >
> > > AUDIT_TIME_ADJNTPVAL
> > >
> > > op - which value was adjusted:
> > > offset - corresponding to the time_offset variable
> > > freq - corresponding to the time_freq variable
> > > status - corresponding to the time_status variable
> > > adjust - corresponding to the time_adjust variable
> > > tick - corresponding to the tick_usec variable
> > > tai - corresponding to the timekeeping's TAI offset
> >
> > I understand that reusing "op" is tempting, but the above aren't
> > really operations, they are state variables which are being changed.
> > Using the CONFIG_CHANGE record as a basis, I wonder if we are better
> >
> > off with something like the following:
> > type=TIME_CHANGE <var>=<value_new> old=<value_old>
> >
> > ... you might need to preface the variable names with something like
> > "ntp_" or "offset_". You'll notice I'm also suggesting we use a
> > single record type here; is there any reason why two records types are
> > required?
>
> Why not do something like:
>
> type=TIME_CHANGE var=<var> new=<value_new> old=<value_old>
>
> So that we don't pollute the field namespace *and* create 8 variants on
> the same record format? This shouldn't be much of a concern with binary
> record formats, but we're stuck with the current parsing scheme for now.

Something like this or the other format is fine. Neither hurt parsing because
these are not searchable fields. We only have issues when it involves a
searchable field name.

HTH...

-Steve

> > > old - the old value
> > > new - the new value
> > >
> > > Signed-off-by: Ondrej Mosnacek <[email protected]>
> > > ---
> > >
> > > include/linux/audit.h | 21 +++++++++++++++++++++
> > > include/uapi/linux/audit.h | 2 ++
> > > kernel/auditsc.c | 15 +++++++++++++++
> > > 3 files changed, 38 insertions(+)
> >
> > A reminder that we need tests for these new records and a RFE page on the
> > wiki:
> >
> > * https://github.com/linux-audit/audit-testsuite
> > * https://github.com/linux-audit/audit-kernel/wiki
>
> - RGB
>
> --
> Richard Guy Briggs <[email protected]>
> Sr. S/W Engineer, Kernel Security, Base Operating Systems
> Remote, Ottawa, Red Hat Canada
> IRC: rgb, SunRaycer
> Voice: +1.647.777.2635, Internal: (81) 32635





2018-09-14 16:30:30

by Richard Guy Briggs

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On 2018-09-14 11:34, Steve Grubb wrote:
> On Friday, September 14, 2018 11:16:43 AM EDT Richard Guy Briggs wrote:
> > On 2018-09-13 23:18, Paul Moore wrote:
> > > On Fri, Aug 24, 2018 at 8:00 AM Ondrej Mosnacek <[email protected]>
> wrote:
> > > > This patch adds two auxiliary record types that will be used to
> > > > annotate
> > > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > > been changed.
> > > >
> > > > Next, it adds two functions to the audit interface:
> > > > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > > >
> > > > offset is injected by a syscall from userspace,
> > > >
> > > > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > > >
> > > > variable is changed by a syscall from userspace.
> > > >
> > > > Quick reference for the fields of the new records:
> > > > AUDIT_TIME_INJOFFSET
> > > >
> > > > sec - the 'seconds' part of the offset
> > > > nsec - the 'nanoseconds' part of the offset
> > > >
> > > > AUDIT_TIME_ADJNTPVAL
> > > >
> > > > op - which value was adjusted:
> > > > offset - corresponding to the time_offset variable
> > > > freq - corresponding to the time_freq variable
> > > > status - corresponding to the time_status variable
> > > > adjust - corresponding to the time_adjust variable
> > > > tick - corresponding to the tick_usec variable
> > > > tai - corresponding to the timekeeping's TAI offset
> > >
> > > I understand that reusing "op" is tempting, but the above aren't
> > > really operations, they are state variables which are being changed.
> > > Using the CONFIG_CHANGE record as a basis, I wonder if we are better
> > >
> > > off with something like the following:
> > > type=TIME_CHANGE <var>=<value_new> old=<value_old>
> > >
> > > ... you might need to preface the variable names with something like
> > > "ntp_" or "offset_". You'll notice I'm also suggesting we use a
> > > single record type here; is there any reason why two records types are
> > > required?
> >
> > Why not do something like:
> >
> > type=TIME_CHANGE var=<var> new=<value_new> old=<value_old>
> >
> > So that we don't pollute the field namespace *and* create 8 variants on
> > the same record format? This shouldn't be much of a concern with binary
> > record formats, but we're stuck with the current parsing scheme for now.
>
> Something like this or the other format is fine. Neither hurt parsing because
> these are not searchable fields. We only have issues when it involves a
> searchable field name.

Ok, fair enough. Thanks Steve.

> HTH...
>
> -Steve
>
> > > > old - the old value
> > > > new - the new value
> > > >
> > > > Signed-off-by: Ondrej Mosnacek <[email protected]>
> > > > ---
> > > >
> > > > include/linux/audit.h | 21 +++++++++++++++++++++
> > > > include/uapi/linux/audit.h | 2 ++
> > > > kernel/auditsc.c | 15 +++++++++++++++
> > > > 3 files changed, 38 insertions(+)
> > >
> > > A reminder that we need tests for these new records and a RFE page on the
> > > wiki:
> > >
> > > * https://github.com/linux-audit/audit-testsuite
> > > * https://github.com/linux-audit/audit-kernel/wiki
> >
> > - RGB

- RGB

--
Richard Guy Briggs <[email protected]>
Sr. S/W Engineer, Kernel Security, Base Operating Systems
Remote, Ottawa, Red Hat Canada
IRC: rgb, SunRaycer
Voice: +1.647.777.2635, Internal: (81) 32635

2018-09-17 12:33:29

by Ondrej Mosnacek

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Thu, Sep 13, 2018 at 5:19 PM Richard Guy Briggs <[email protected]> wrote:
> On 2018-09-13 15:59, Ondrej Mosnacek wrote:
> > On Mon, Aug 27, 2018 at 6:38 PM Steve Grubb <[email protected]> wrote:
> > > On Monday, August 27, 2018 5:13:17 AM EDT Ondrej Mosnacek wrote:
> > > > On Mon, Aug 27, 2018 at 9:50 AM Miroslav Lichvar <[email protected]>
> > > wrote:
> > > > > On Fri, Aug 24, 2018 at 02:00:00PM +0200, Ondrej Mosnacek wrote:
> > > > > > This patch adds two auxiliary record types that will be used to
> > > > > > annotate
> > > > > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > > > > been changed.
> > > > >
> > > > > It seems the "adjust" function intentionally logs also calls/modes
> > > > > that don't actually change anything. Can you please explain it a bit
> > > > > in the message?
> > > > >
> > > > > NTP/PTP daemons typically don't read the adjtimex values in a normal
> > > > > operation and overwrite them on each update, even if they don't
> > > > > change. If the audit function checked that oldval != newval, the
> > > > > number of messages would be reduced and it might be easier to follow.
> > > >
> > > > We actually want to log any attempt to change a value, as even an
> > > > intention to set/change something could be a hint that the process is
> > > > trying to do something bad (see discussion at [1]).
> > >
> > > One of the problems is that these applications can flood the logs very
> > > quickly. An attempt to change is not needed unless it fails for permissions
> > > reasons. So, limiting to actual changes is probably a good thing.
> >
> > Well, Richard seemed to "violently" agree with the opposite, so now I
> > don't know which way to go... Paul, you are the official tie-breaker
> > here, which do you prefer?
>
> The circumstances have changed with new information being added. I
> recall violently agreeing several iterations ago with your previous
> assessment, which has also changed with this new information. I'd agree
> with Steve that a flood of information about something that did not
> change value could hide important information.

OK, understood.

> (BTW: The expression "to violoently agree with" is generally used in a
> situation where two parties appear to have been arguing two different
> sides of an issue and then realize they have much more in common than
> initially apparent.)

I see, thanks for the explanation! I didn't know that expression
before, so I think I took it a bit too literally :)

>
> > > -Steve
> > >
> > > > There are valid
> > > > arguments both for and against this choice, but we have to pick one in
> > > > the end... Anyway, I should explain the reasoning in the commit
> > > > message better, right now it just states the fact without explanation
> > > > (in the second patch), thank you for pointing my attention to it.
> > > >
> > > > [1] https://www.redhat.com/archives/linux-audit/2018-July/msg00061.html
> > > >
> > > > --
> > > > Ondrej Mosnacek <omosnace at redhat dot com>
> >
> > Ondrej Mosnacek <omosnace at redhat dot com>
>
> - RGB
>
> --
> Richard Guy Briggs <[email protected]>
> Sr. S/W Engineer, Kernel Security, Base Operating Systems
> Remote, Ottawa, Red Hat Canada
> IRC: rgb, SunRaycer
> Voice: +1.647.777.2635, Internal: (81) 32635

--
Ondrej Mosnacek <omosnace at redhat dot com>Associate Software
Engineer, Security Technologies
Red Hat, Inc.

2018-09-17 12:34:42

by Ondrej Mosnacek

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Thu, Sep 13, 2018 at 5:59 PM Richard Guy Briggs <[email protected]> wrote:
> On 2018-08-27 10:28, Ondrej Mosnacek wrote:
> > On Fri, Aug 24, 2018 at 8:33 PM John Stultz <[email protected]> wrote:
> > > On Fri, Aug 24, 2018 at 5:00 AM, Ondrej Mosnacek <[email protected]> wrote:
> > > > This patch adds two auxiliary record types that will be used to annotate
> > > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > > been changed.
> > > >
> > > > Next, it adds two functions to the audit interface:
> > > > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > > > offset is injected by a syscall from userspace,
> > > > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > > > variable is changed by a syscall from userspace.
> > > >
> > > > Quick reference for the fields of the new records:
> > > > AUDIT_TIME_INJOFFSET
> > > > sec - the 'seconds' part of the offset
> > > > nsec - the 'nanoseconds' part of the offset
> > > > AUDIT_TIME_ADJNTPVAL
> > > > op - which value was adjusted:
> > > > offset - corresponding to the time_offset variable
> > > > freq - corresponding to the time_freq variable
> > > > status - corresponding to the time_status variable
> > > > adjust - corresponding to the time_adjust variable
> > > > tick - corresponding to the tick_usec variable
> > > > tai - corresponding to the timekeeping's TAI offset
> > > > old - the old value
> > > > new - the new value
> > > >
> > > > Signed-off-by: Ondrej Mosnacek <[email protected]>
> > > > ---
> > > > include/linux/audit.h | 21 +++++++++++++++++++++
> > > > include/uapi/linux/audit.h | 2 ++
> > > > kernel/auditsc.c | 15 +++++++++++++++
> > > > 3 files changed, 38 insertions(+)
> > > >
> > > > diff --git a/include/linux/audit.h b/include/linux/audit.h
> > > > index 9334fbef7bae..0d084d4b4042 100644
> > > > --- a/include/linux/audit.h
> > > > +++ b/include/linux/audit.h
> > > > @@ -26,6 +26,7 @@
> > > > #include <linux/sched.h>
> > > > #include <linux/ptrace.h>
> > > > #include <uapi/linux/audit.h>
> > > > +#include <uapi/linux/timex.h>
> > > >
> > > > #define AUDIT_INO_UNSET ((unsigned long)-1)
> > > > #define AUDIT_DEV_UNSET ((dev_t)-1)
> > > > @@ -356,6 +357,8 @@ extern void __audit_log_capset(const struct cred *new, const struct cred *old);
> > > > extern void __audit_mmap_fd(int fd, int flags);
> > > > extern void __audit_log_kern_module(char *name);
> > > > extern void __audit_fanotify(unsigned int response);
> > > > +extern void __audit_tk_injoffset(struct timespec64 offset);
> > > > +extern void __audit_ntp_adjust(const char *type, s64 oldval, s64 newval);
> > > >
> > > > static inline void audit_ipc_obj(struct kern_ipc_perm *ipcp)
> > > > {
> > > > @@ -458,6 +461,18 @@ static inline void audit_fanotify(unsigned int response)
> > > > __audit_fanotify(response);
> > > > }
> > > >
> > > > +static inline void audit_tk_injoffset(struct timespec64 offset)
> > > > +{
> > > > + if (!audit_dummy_context())
> > > > + __audit_tk_injoffset(offset);
> > > > +}
> > > > +
> > > > +static inline void audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> > > > +{
> > > > + if (!audit_dummy_context())
> > > > + __audit_ntp_adjust(type, oldval, newval);
> > > > +}
> > > > +
> > > > extern int audit_n_rules;
> > > > extern int audit_signals;
> > > > #else /* CONFIG_AUDITSYSCALL */
> > > > @@ -584,6 +599,12 @@ static inline void audit_log_kern_module(char *name)
> > > > static inline void audit_fanotify(unsigned int response)
> > > > { }
> > > >
> > > > +static inline void audit_tk_injoffset(struct timespec64 offset)
> > > > +{ }
> > > > +
> > > > +static inline void audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> > > > +{ }
> > > > +
> > > > static inline void audit_ptrace(struct task_struct *t)
> > > > { }
> > > > #define audit_n_rules 0
> > > > diff --git a/include/uapi/linux/audit.h b/include/uapi/linux/audit.h
> > > > index 4e3eaba84175..242ce562b41a 100644
> > > > --- a/include/uapi/linux/audit.h
> > > > +++ b/include/uapi/linux/audit.h
> > > > @@ -114,6 +114,8 @@
> > > > #define AUDIT_REPLACE 1329 /* Replace auditd if this packet unanswerd */
> > > > #define AUDIT_KERN_MODULE 1330 /* Kernel Module events */
> > > > #define AUDIT_FANOTIFY 1331 /* Fanotify access decision */
> > > > +#define AUDIT_TIME_INJOFFSET 1332 /* Timekeeping offset injected */
> > > > +#define AUDIT_TIME_ADJNTPVAL 1333 /* NTP value adjustment */
> > > >
> > > > #define AUDIT_AVC 1400 /* SE Linux avc denial or grant */
> > > > #define AUDIT_SELINUX_ERR 1401 /* Internal SE Linux Errors */
> > > > diff --git a/kernel/auditsc.c b/kernel/auditsc.c
> > > > index fb207466e99b..d355d32d9765 100644
> > > > --- a/kernel/auditsc.c
> > > > +++ b/kernel/auditsc.c
> > > > @@ -2422,6 +2422,21 @@ void __audit_fanotify(unsigned int response)
> > > > AUDIT_FANOTIFY, "resp=%u", response);
> > > > }
> > > >
> > > > +/* We need to allocate with GFP_ATOMIC here, since these two functions will be
> > > > + * called while holding the timekeeping lock: */
> > > > +void __audit_tk_injoffset(struct timespec64 offset)
> > > > +{
> > > > + audit_log(audit_context(), GFP_ATOMIC, AUDIT_TIME_INJOFFSET,
> > > > + "sec=%lli nsec=%li", (long long)offset.tv_sec, offset.tv_nsec);
> > > > +}
> > > > +
> > > > +void __audit_ntp_adjust(const char *type, s64 oldval, s64 newval)
> > > > +{
> > > > + audit_log(audit_context(), GFP_ATOMIC, AUDIT_TIME_ADJNTPVAL,
> > > > + "op=%s old=%lli new=%lli", type,
> > > > + (long long)oldval, (long long)newval);
> > > > +}
> > >
> > > So it looks like you've been careful here, but just want to make sure,
> > > nothing in the audit_log path calls anything that might possibly call
> > > back into timekeeping code? We've had a number of issues over time
> > > where debug calls out to other subsystems end up getting tweaked to
> > > add some timestamping and we deadlock. :)
> >
> > Hm, that's a good point... AFAIK, the only place where audit_log()
> > might call into timekeeping is when it captures the timestamp for the
> > record (via ktime_get_coarse_real_ts64()), but this is only called
> > when the functions are called outside of a syscall and that should
> > never happen here. I'm thinking I could harden this more by returning
> > early from these functions if WARN_ON_ONCE(!audit_context() ||
> > !audit_context()->in_syscall)... It is not a perfect solution, but at
> > least there will be something in the code reminding us about this
> > issue.
>
> It looks like this should be safe already since if there is no context,
> it won't call into the real audit logging function and as you point out,
> this should never happen outside of a syscall.
>
> > > One approach we've done to make sure we don't create a trap for future
> > > changes in other subsystems, is avoid calling into other subsystems
> > > until later when we've dropped the locks, (see clock_was_set). Its a
> > > little rough for some of the things done deep in the ntp code, but
> > > might be an extra cautious approach to try.
> >
> > I'm afraid that delaying the audit_* calls would complicate things too
> > much here... Paul/Richard, any thoughts?
>
> The other way to address this would be to have your timekeeping audit
> logging functions save the parameters you want to log somewhere in the
> struct audit_context union and have it print the record on syscall exit
> based on the presence of this type and applicable filters.

Yes, I thought about that, but it doesn't seem to be worth the hassle.
I think I'll just add the paranoid WARN_ON_ONCE(...) unless there are
strong objections to that.

>
> > Ondrej Mosnacek <omosnace at redhat dot com>
>
> - RGB
>
> --
> Richard Guy Briggs <[email protected]>
> Sr. S/W Engineer, Kernel Security, Base Operating Systems
> Remote, Ottawa, Red Hat Canada
> IRC: rgb, SunRaycer
> Voice: +1.647.777.2635, Internal: (81) 32635

--
Ondrej Mosnacek <omosnace at redhat dot com>Associate Software
Engineer, Security Technologies
Red Hat, Inc.

2018-09-17 12:34:46

by Ondrej Mosnacek

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Fri, Sep 14, 2018 at 5:09 AM Paul Moore <[email protected]> wrote:
> On Thu, Sep 13, 2018 at 9:59 AM Ondrej Mosnacek <[email protected]> wrote:
> > On Mon, Aug 27, 2018 at 6:38 PM Steve Grubb <[email protected]> wrote:
> > > On Monday, August 27, 2018 5:13:17 AM EDT Ondrej Mosnacek wrote:
> > > > On Mon, Aug 27, 2018 at 9:50 AM Miroslav Lichvar <[email protected]>
> > > wrote:
> > > > > On Fri, Aug 24, 2018 at 02:00:00PM +0200, Ondrej Mosnacek wrote:
> > > > > > This patch adds two auxiliary record types that will be used to
> > > > > > annotate
> > > > > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > > > > been changed.
> > > > >
> > > > > It seems the "adjust" function intentionally logs also calls/modes
> > > > > that don't actually change anything. Can you please explain it a bit
> > > > > in the message?
> > > > >
> > > > > NTP/PTP daemons typically don't read the adjtimex values in a normal
> > > > > operation and overwrite them on each update, even if they don't
> > > > > change. If the audit function checked that oldval != newval, the
> > > > > number of messages would be reduced and it might be easier to follow.
> > > >
> > > > We actually want to log any attempt to change a value, as even an
> > > > intention to set/change something could be a hint that the process is
> > > > trying to do something bad (see discussion at [1]).
> > >
> > > One of the problems is that these applications can flood the logs very
> > > quickly. An attempt to change is not needed unless it fails for permissions
> > > reasons. So, limiting to actual changes is probably a good thing.
> >
> > Well, Richard seemed to "violently" agree with the opposite, so now I
> > don't know which way to go... Paul, you are the official tie-breaker
> > here, which do you prefer?
>
> The general idea is that we only care about *changes* to the system
> state, so if a process is setting a variable to with a value that
> matches it's current value I see no reason why we need to generate a
> change record.
>
> Another thing to keep in mind, we can always change the behavior to be
> more verbose (*always* generate a record, regardless of value) without
> likely causing a regression, but limiting records is more difficult
> and more likely to cause regressions.

OK, that makes sense. I'll limit logging to actual changes in the next revision.

>
> --
> paul moore
> http://www.paul-moore.com

--
Ondrej Mosnacek <omosnace at redhat dot com>
Associate Software Engineer, Security Technologies
Red Hat, Inc.

2018-09-17 12:41:10

by Ondrej Mosnacek

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Fri, Sep 14, 2018 at 5:19 AM Paul Moore <[email protected]> wrote:
> On Fri, Aug 24, 2018 at 8:00 AM Ondrej Mosnacek <[email protected]> wrote:
> > This patch adds two auxiliary record types that will be used to annotate
> > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > been changed.
> >
> > Next, it adds two functions to the audit interface:
> > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > offset is injected by a syscall from userspace,
> > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > variable is changed by a syscall from userspace.
> >
> > Quick reference for the fields of the new records:
> > AUDIT_TIME_INJOFFSET
> > sec - the 'seconds' part of the offset
> > nsec - the 'nanoseconds' part of the offset
> > AUDIT_TIME_ADJNTPVAL
> > op - which value was adjusted:
> > offset - corresponding to the time_offset variable
> > freq - corresponding to the time_freq variable
> > status - corresponding to the time_status variable
> > adjust - corresponding to the time_adjust variable
> > tick - corresponding to the tick_usec variable
> > tai - corresponding to the timekeeping's TAI offset
>
> I understand that reusing "op" is tempting, but the above aren't
> really operations, they are state variables which are being changed.

I remember Steve (or was it Richard?) convincing me at one of the
meetings that "op" is the right filed name to use, despite it not
being a name for an operation... But I don't really care, I'm okay
with changing it to e.g. "var" as Richard suggests later in this
thread.

> Using the CONFIG_CHANGE record as a basis, I wonder if we are better
> off with something like the following:
>
> type=TIME_CHANGE <var>=<value_new> old=<value_old>
>
> ... you might need to preface the variable names with something like
> "ntp_" or "offset_". You'll notice I'm also suggesting we use a
> single record type here; is there any reason why two records types are
> required?

There are actually two reasons:
1. The injected offset is a timespec64, so it consists of two integer
values (and it would be weird to produce two records for it, since IMO
it is conceptually still a single variable).
2. In all other cases the variable is reset to the (possibly
transformed) input value, while in this case the input value is added
directly to the system time. This can be viewed as a kind of variable
too, but it would be weird to report old and new value for it, since
its value flows with time.

Plus, when I look at:
type=TIME_INJOFFSET [...]: sec=-16 nsec=124887145

I can immediately see that the time was shifted back by 16-something
seconds, while when I look at something like:

type=TIME_CHANGE [...]: var=time_sec new=1537185685 old=1537185701
type=TIME_CHANGE [...]: var=time_nsec new=664373417 old=789260562

I can just see some big numbers that I need to do math with before I
get an idea of what is the magnitude (or sign) of the change.

>
> > old - the old value
> > new - the new value
> >
> > Signed-off-by: Ondrej Mosnacek <[email protected]>
> > ---
> > include/linux/audit.h | 21 +++++++++++++++++++++
> > include/uapi/linux/audit.h | 2 ++
> > kernel/auditsc.c | 15 +++++++++++++++
> > 3 files changed, 38 insertions(+)
>
> A reminder that we need tests for these new records and a RFE page on the wiki:
>
> * https://github.com/linux-audit/audit-testsuite

I was going to start working on this once the format issues are
settled. (Although I probably should have kept the RFC in the subject
until then...)

> * https://github.com/linux-audit/audit-kernel/wiki

I admit I forgot about this duty, but again I would like to wait for
the discussions to settle before writing that up.

>
> --
> paul moore
> http://www.paul-moore.com--
Ondrej Mosnacek <omosnace at redhat dot com>
Associate Software Engineer, Security Technologies
Red Hat, Inc.

2018-09-17 14:25:59

by Richard Guy Briggs

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On 2018-09-17 14:38, Ondrej Mosnacek wrote:
> On Fri, Sep 14, 2018 at 5:19 AM Paul Moore <[email protected]> wrote:
> > On Fri, Aug 24, 2018 at 8:00 AM Ondrej Mosnacek <[email protected]> wrote:
> > > This patch adds two auxiliary record types that will be used to annotate
> > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > been changed.
> > >
> > > Next, it adds two functions to the audit interface:
> > > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > > offset is injected by a syscall from userspace,
> > > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > > variable is changed by a syscall from userspace.
> > >
> > > Quick reference for the fields of the new records:
> > > AUDIT_TIME_INJOFFSET
> > > sec - the 'seconds' part of the offset
> > > nsec - the 'nanoseconds' part of the offset
> > > AUDIT_TIME_ADJNTPVAL
> > > op - which value was adjusted:
> > > offset - corresponding to the time_offset variable
> > > freq - corresponding to the time_freq variable
> > > status - corresponding to the time_status variable
> > > adjust - corresponding to the time_adjust variable
> > > tick - corresponding to the tick_usec variable
> > > tai - corresponding to the timekeeping's TAI offset
> >
> > I understand that reusing "op" is tempting, but the above aren't
> > really operations, they are state variables which are being changed.
>
> I remember Steve (or was it Richard?) convincing me at one of the
> meetings that "op" is the right filed name to use, despite it not
> being a name for an operation... But I don't really care, I'm okay
> with changing it to e.g. "var" as Richard suggests later in this
> thread.
>
> > Using the CONFIG_CHANGE record as a basis, I wonder if we are better
> > off with something like the following:
> >
> > type=TIME_CHANGE <var>=<value_new> old=<value_old>
> >
> > ... you might need to preface the variable names with something like
> > "ntp_" or "offset_". You'll notice I'm also suggesting we use a
> > single record type here; is there any reason why two records types are
> > required?
>
> There are actually two reasons:
> 1. The injected offset is a timespec64, so it consists of two integer
> values (and it would be weird to produce two records for it, since IMO
> it is conceptually still a single variable).
> 2. In all other cases the variable is reset to the (possibly
> transformed) input value, while in this case the input value is added
> directly to the system time. This can be viewed as a kind of variable
> too, but it would be weird to report old and new value for it, since
> its value flows with time.
>
> Plus, when I look at:
> type=TIME_INJOFFSET [...]: sec=-16 nsec=124887145
>
> I can immediately see that the time was shifted back by 16-something
> seconds, while when I look at something like:
>
> type=TIME_CHANGE [...]: var=time_sec new=1537185685 old=1537185701
> type=TIME_CHANGE [...]: var=time_nsec new=664373417 old=789260562
>
> I can just see some big numbers that I need to do math with before I
> get an idea of what is the magnitude (or sign) of the change.
>
> > > old - the old value
> > > new - the new value
> > >
> > > Signed-off-by: Ondrej Mosnacek <[email protected]>
> > > ---
> > > include/linux/audit.h | 21 +++++++++++++++++++++
> > > include/uapi/linux/audit.h | 2 ++
> > > kernel/auditsc.c | 15 +++++++++++++++
> > > 3 files changed, 38 insertions(+)
> >
> > A reminder that we need tests for these new records and a RFE page on the wiki:
> >
> > * https://github.com/linux-audit/audit-testsuite
>
> I was going to start working on this once the format issues are
> settled. (Although I probably should have kept the RFC in the subject
> until then...)

There are more iterative development methods (to which we appear to be
trying to steer) where the test is written first, partly helping clarify
what the goal is. Refining the test is incremental.

> > * https://github.com/linux-audit/audit-kernel/wiki
>
> I admit I forgot about this duty, but again I would like to wait for
> the discussions to settle before writing that up.

While tempting to leave it to the end, documenting it initially can help
clarify in your own mind what you are trying to accomplish and can help
others understand what you are trying to do.

> > paul moore

> Ondrej Mosnacek <omosnace at redhat dot com>

- RGB

--
Richard Guy Briggs <[email protected]>
Sr. S/W Engineer, Kernel Security, Base Operating Systems
Remote, Ottawa, Red Hat Canada
IRC: rgb, SunRaycer
Voice: +1.647.777.2635, Internal: (81) 32635

2018-09-17 14:41:39

by Paul Moore

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Fri, Sep 14, 2018 at 11:21 AM Richard Guy Briggs <[email protected]> wrote:
> On 2018-09-13 23:18, Paul Moore wrote:
> > On Fri, Aug 24, 2018 at 8:00 AM Ondrej Mosnacek <[email protected]> wrote:
> > > This patch adds two auxiliary record types that will be used to annotate
> > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > been changed.
> > >
> > > Next, it adds two functions to the audit interface:
> > > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > > offset is injected by a syscall from userspace,
> > > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > > variable is changed by a syscall from userspace.
> > >
> > > Quick reference for the fields of the new records:
> > > AUDIT_TIME_INJOFFSET
> > > sec - the 'seconds' part of the offset
> > > nsec - the 'nanoseconds' part of the offset
> > > AUDIT_TIME_ADJNTPVAL
> > > op - which value was adjusted:
> > > offset - corresponding to the time_offset variable
> > > freq - corresponding to the time_freq variable
> > > status - corresponding to the time_status variable
> > > adjust - corresponding to the time_adjust variable
> > > tick - corresponding to the tick_usec variable
> > > tai - corresponding to the timekeeping's TAI offset
> >
> > I understand that reusing "op" is tempting, but the above aren't
> > really operations, they are state variables which are being changed.
> > Using the CONFIG_CHANGE record as a basis, I wonder if we are better
> > off with something like the following:
> >
> > type=TIME_CHANGE <var>=<value_new> old=<value_old>
> >
> > ... you might need to preface the variable names with something like
> > "ntp_" or "offset_". You'll notice I'm also suggesting we use a
> > single record type here; is there any reason why two records types are
> > required?
>
> Why not do something like:
>
> type=TIME_CHANGE var=<var> new=<value_new> old=<value_old>
>
> So that we don't pollute the field namespace *and* create 8 variants on
> the same record format? This shouldn't be much of a concern with binary
> record formats, but we're stuck with the current parsing scheme for now.

Since there is already some precedence with the "<var>=<value_new>"
format, and the field namespace is already a bit of a mess IMHO, I'd
like us to stick with the style used by CONFIG_CHANGE.

--
paul moore
http://www.paul-moore.com

2018-09-17 14:52:14

by Paul Moore

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Mon, Sep 17, 2018 at 8:38 AM Ondrej Mosnacek <[email protected]> wrote:
>
> On Fri, Sep 14, 2018 at 5:19 AM Paul Moore <[email protected]> wrote:
> > On Fri, Aug 24, 2018 at 8:00 AM Ondrej Mosnacek <[email protected]> wrote:
> > > This patch adds two auxiliary record types that will be used to annotate
> > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > been changed.
> > >
> > > Next, it adds two functions to the audit interface:
> > > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > > offset is injected by a syscall from userspace,
> > > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > > variable is changed by a syscall from userspace.
> > >
> > > Quick reference for the fields of the new records:
> > > AUDIT_TIME_INJOFFSET
> > > sec - the 'seconds' part of the offset
> > > nsec - the 'nanoseconds' part of the offset
> > > AUDIT_TIME_ADJNTPVAL
> > > op - which value was adjusted:
> > > offset - corresponding to the time_offset variable
> > > freq - corresponding to the time_freq variable
> > > status - corresponding to the time_status variable
> > > adjust - corresponding to the time_adjust variable
> > > tick - corresponding to the tick_usec variable
> > > tai - corresponding to the timekeeping's TAI offset
> >
> > I understand that reusing "op" is tempting, but the above aren't
> > really operations, they are state variables which are being changed.
>
> I remember Steve (or was it Richard?) convincing me at one of the
> meetings that "op" is the right filed name to use, despite it not
> being a name for an operation... But I don't really care, I'm okay
> with changing it to e.g. "var" as Richard suggests later in this
> thread.

As I said before, this seems like an abuse of the "op" field.

> > Using the CONFIG_CHANGE record as a basis, I wonder if we are better
> > off with something like the following:
> >
> > type=TIME_CHANGE <var>=<value_new> old=<value_old>
> >
> > ... you might need to preface the variable names with something like
> > "ntp_" or "offset_". You'll notice I'm also suggesting we use a
> > single record type here; is there any reason why two records types are
> > required?
>
> There are actually two reasons:
> 1. The injected offset is a timespec64, so it consists of two integer
> values (and it would be weird to produce two records for it, since IMO
> it is conceptually still a single variable).
> 2. In all other cases the variable is reset to the (possibly
> transformed) input value, while in this case the input value is added
> directly to the system time. This can be viewed as a kind of variable
> too, but it would be weird to report old and new value for it, since
> its value flows with time.
>
> Plus, when I look at:
> type=TIME_INJOFFSET [...]: sec=-16 nsec=124887145
>
> I can immediately see that the time was shifted back by 16-something
> seconds, while when I look at something like:
>
> type=TIME_CHANGE [...]: var=time_sec new=1537185685 old=1537185701
> type=TIME_CHANGE [...]: var=time_nsec new=664373417 old=789260562
>
> I can just see some big numbers that I need to do math with before I
> get an idea of what is the magnitude (or sign) of the change.

Okay, with that in mind, perhaps when recording the offset values we
omit the "old" values (arguably that doesn't make much sense here) and
keep the sec/nsec split:

type=TIME_CHANGE [...]: offset_sec=<X> offset_nsec=<Y>

... and for all others we stick with:

type=TIME_CHANGE [...]: ntp_<VAR>=<NEWVAL> old=<OLD_VAL>

... and if that results in multiple TIME_CHANGE records for a given
event, that's fine with me.

> > A reminder that we need tests for these new records and a RFE page on the wiki:
> >
> > * https://github.com/linux-audit/audit-testsuite
>
> I was going to start working on this once the format issues are
> settled. (Although I probably should have kept the RFC in the subject
> until then...)
>
> > * https://github.com/linux-audit/audit-kernel/wiki
>
> I admit I forgot about this duty, but again I would like to wait for
> the discussions to settle before writing that up.

That is fine, do it in whatever order works best for you, just
understand that I'm probably not going to merge patches like this
until I see both documentation and tests.

--
paul moore
http://www.paul-moore.com

2018-09-21 11:21:59

by Ondrej Mosnacek

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Mon, Sep 17, 2018 at 4:51 PM Paul Moore <[email protected]> wrote:
> On Mon, Sep 17, 2018 at 8:38 AM Ondrej Mosnacek <[email protected]> wrote:
> >
> > On Fri, Sep 14, 2018 at 5:19 AM Paul Moore <[email protected]> wrote:
> > > On Fri, Aug 24, 2018 at 8:00 AM Ondrej Mosnacek <[email protected]> wrote:
> > > > This patch adds two auxiliary record types that will be used to annotate
> > > > the adjtimex SYSCALL records with the NTP/timekeeping values that have
> > > > been changed.
> > > >
> > > > Next, it adds two functions to the audit interface:
> > > > - audit_tk_injoffset(), which will be called whenever a timekeeping
> > > > offset is injected by a syscall from userspace,
> > > > - audit_ntp_adjust(), which will be called whenever an NTP internal
> > > > variable is changed by a syscall from userspace.
> > > >
> > > > Quick reference for the fields of the new records:
> > > > AUDIT_TIME_INJOFFSET
> > > > sec - the 'seconds' part of the offset
> > > > nsec - the 'nanoseconds' part of the offset
> > > > AUDIT_TIME_ADJNTPVAL
> > > > op - which value was adjusted:
> > > > offset - corresponding to the time_offset variable
> > > > freq - corresponding to the time_freq variable
> > > > status - corresponding to the time_status variable
> > > > adjust - corresponding to the time_adjust variable
> > > > tick - corresponding to the tick_usec variable
> > > > tai - corresponding to the timekeeping's TAI offset
> > >
> > > I understand that reusing "op" is tempting, but the above aren't
> > > really operations, they are state variables which are being changed.
> >
> > I remember Steve (or was it Richard?) convincing me at one of the
> > meetings that "op" is the right filed name to use, despite it not
> > being a name for an operation... But I don't really care, I'm okay
> > with changing it to e.g. "var" as Richard suggests later in this
> > thread.
>
> As I said before, this seems like an abuse of the "op" field.
>
> > > Using the CONFIG_CHANGE record as a basis, I wonder if we are better
> > > off with something like the following:
> > >
> > > type=TIME_CHANGE <var>=<value_new> old=<value_old>
> > >
> > > ... you might need to preface the variable names with something like
> > > "ntp_" or "offset_". You'll notice I'm also suggesting we use a
> > > single record type here; is there any reason why two records types are
> > > required?
> >
> > There are actually two reasons:
> > 1. The injected offset is a timespec64, so it consists of two integer
> > values (and it would be weird to produce two records for it, since IMO
> > it is conceptually still a single variable).
> > 2. In all other cases the variable is reset to the (possibly
> > transformed) input value, while in this case the input value is added
> > directly to the system time. This can be viewed as a kind of variable
> > too, but it would be weird to report old and new value for it, since
> > its value flows with time.
> >
> > Plus, when I look at:
> > type=TIME_INJOFFSET [...]: sec=-16 nsec=124887145
> >
> > I can immediately see that the time was shifted back by 16-something
> > seconds, while when I look at something like:
> >
> > type=TIME_CHANGE [...]: var=time_sec new=1537185685 old=1537185701
> > type=TIME_CHANGE [...]: var=time_nsec new=664373417 old=789260562
> >
> > I can just see some big numbers that I need to do math with before I
> > get an idea of what is the magnitude (or sign) of the change.
>
> Okay, with that in mind, perhaps when recording the offset values we
> omit the "old" values (arguably that doesn't make much sense here) and
> keep the sec/nsec split:
>
> type=TIME_CHANGE [...]: offset_sec=<X> offset_nsec=<Y>
>
> ... and for all others we stick with:
>
> type=TIME_CHANGE [...]: ntp_<VAR>=<NEWVAL> old=<OLD_VAL>

Alright, that format would work. However, I would still like to have a
separate type for the offset injection, since it has different field
structure and semantics (difference vs. new+old). I don't see any
reason to sacrifice the distinction for just one record type slot
(AFAIK we technically still have about 2 billion left...).

(Maybe you just duplicated the record type by mistake, in that case
please disregard the last sentence.)

>
> ... and if that results in multiple TIME_CHANGE records for a given
> event, that's fine with me.
>
> > > A reminder that we need tests for these new records and a RFE page on the wiki:
> > >
> > > * https://github.com/linux-audit/audit-testsuite
> >
> > I was going to start working on this once the format issues are
> > settled. (Although I probably should have kept the RFC in the subject
> > until then...)
> >
> > > * https://github.com/linux-audit/audit-kernel/wiki
> >
> > I admit I forgot about this duty, but again I would like to wait for
> > the discussions to settle before writing that up.
>
> That is fine, do it in whatever order works best for you, just
> understand that I'm probably not going to merge patches like this
> until I see both documentation and tests.
>
> --
> paul moore
> http://www.paul-moore.com

--
Ondrej Mosnacek <omosnace at redhat dot com>
Associate Software Engineer, Security Technologies
Red Hat, Inc.

2018-09-22 20:43:11

by Paul Moore

[permalink] [raw]
Subject: Re: [PATCH ghak10 v5 1/2] audit: Add functions to log time adjustments

On Fri, Sep 21, 2018 at 7:21 AM Ondrej Mosnacek <[email protected]> wrote:
> On Mon, Sep 17, 2018 at 4:51 PM Paul Moore <[email protected]> wrote:
> > On Mon, Sep 17, 2018 at 8:38 AM Ondrej Mosnacek <[email protected]> wrote:
> > > On Fri, Sep 14, 2018 at 5:19 AM Paul Moore <[email protected]> wrote:
> > > > On Fri, Aug 24, 2018 at 8:00 AM Ondrej Mosnacek <[email protected]> wrote:

...

> > Okay, with that in mind, perhaps when recording the offset values we
> > omit the "old" values (arguably that doesn't make much sense here) and
> > keep the sec/nsec split:
> >
> > type=TIME_CHANGE [...]: offset_sec=<X> offset_nsec=<Y>
> >
> > ... and for all others we stick with:
> >
> > type=TIME_CHANGE [...]: ntp_<VAR>=<NEWVAL> old=<OLD_VAL>
>
> Alright, that format would work. However, I would still like to have a
> separate type for the offset injection, since it has different field
> structure and semantics (difference vs. new+old). I don't see any
> reason to sacrifice the distinction for just one record type slot
> (AFAIK we technically still have about 2 billion left...).
>
> (Maybe you just duplicated the record type by mistake, in that case
> please disregard the last sentence.)

A reasonable guess on the typo, but no that was intentional :)

As described above, there is no set field ordering for the TIME_CHANGE
record, just like there is not set field ordering for the
CONFIG_CHANGE record. Why? We only include the state variables that
are being changed instead of including all of the available state
variables. Yes, historically there are the "new" and "old" fields,
but I don't see that as a strong convention; the special "old=" field
name helps prevent confusion.

Yes, we aren't really at risk of running out of record types, but why
do we *need* two types here? I don't believe the ordering/structure
argument is significant in this particular case, and I would much
rather see all the time related state changes included in one
TIME_CHANGE record.

--
paul moore
http://www.paul-moore.com