diff mbox series

[v2,3/3] syscalls/clock_adjtime: create clock_adjtime syscall tests

Message ID 20190320214135.7029-3-rafael.tinoco@linaro.org
State Superseded
Headers show
Series [v2,1/3] lib: include SAFE_CLOCK_ADJTIME() macro | expand

Commit Message

Rafael David Tinoco March 20, 2019, 9:41 p.m. UTC
Fixes: 270

clock_adjtime{01,02} are created using the new API, based on existing
adjtimex(2) tests. clock_adjtime() syscall might have as execution
path:

 1) a regular POSIX clock (only REALTIME clock implements adjtime())
    - will behave exactly like adjtimex() system call.
    - only one being tested here.

 2) a dynamic POSIX clock (which ops are implemented by PTP clocks)
    - will trigger the PTP clock driver function "adjtime()"
    - different implementations from one PTP clock to another
    - might return EOPNOTSUPP (like ptp_kvm_caps, for example)
    - no observed execution entry point for clock_adjtime()

Signed-off-by: Rafael David Tinoco <rafael.tinoco@linaro.org>
---
 include/lapi/posix_clocks.h                   |   4 +
 runtest/syscalls                              |   3 +
 .../kernel/syscalls/clock_adjtime/.gitignore  |   2 +
 .../kernel/syscalls/clock_adjtime/Makefile    |  10 +
 .../syscalls/clock_adjtime/clock_adjtime.h    |  57 +++++
 .../syscalls/clock_adjtime/clock_adjtime01.c  | 216 +++++++++++++++++
 .../syscalls/clock_adjtime/clock_adjtime02.c  | 218 ++++++++++++++++++
 7 files changed, 510 insertions(+)
 create mode 100644 testcases/kernel/syscalls/clock_adjtime/.gitignore
 create mode 100644 testcases/kernel/syscalls/clock_adjtime/Makefile
 create mode 100644 testcases/kernel/syscalls/clock_adjtime/clock_adjtime.h
 create mode 100644 testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c
 create mode 100644 testcases/kernel/syscalls/clock_adjtime/clock_adjtime02.c

Comments

Rafael David Tinoco March 20, 2019, 9:48 p.m. UTC | #1
> On 20 Mar 2019, at 18:41, Rafael David Tinoco <rafael.tinoco@linaro.org> wrote:
> 
> +	/*
> +	 * special case: EFAULT for NULL pointers
> +	 * txcptr = tst_get_bad_addr() -> SIGSEGV is thrown (and not EFAULT)
> +	 */
> +
> +	if (tc[i].exp_err == EFAULT)
> +		txcptr = NULL;

Cyril,

clock_adjtime() returns an EFAULT in case copy_from_user() returns
errors.

Thing is that, using tst_get_bad_addr(), and having a page with no
permissions, before getting an error from copy_from_user() and returning
- thus getting EFAULT back - we get a SIGSEGV from _copy_from_user()
itself, because it tries to sanitize given address, with memset(0),
before returning to userland (to avoid leaks of some sort ?).

With this, the EFAULT is only got when using NULL pointers, and not a
*bad* pointer… So, right now, I can’tuse tst_get_bad_addr() like you
suggested. Should I remove this test and send a v3 ?

Thanks for reviewing all this.

Best Regards

Rafael D. Tinoco
Cyril Hrubis March 21, 2019, 1:42 p.m. UTC | #2
Hi!
> > +	/*
> > +	 * special case: EFAULT for NULL pointers
> > +	 * txcptr = tst_get_bad_addr() -> SIGSEGV is thrown (and not EFAULT)
> > +	 */
> > +
> > +	if (tc[i].exp_err == EFAULT)
> > +		txcptr = NULL;
> 
> Cyril,
> 
> clock_adjtime() returns an EFAULT in case copy_from_user() returns
> errors.
> 
> Thing is that, using tst_get_bad_addr(), and having a page with no
> permissions, before getting an error from copy_from_user() and returning
> - thus getting EFAULT back - we get a SIGSEGV from _copy_from_user()
> itself, because it tries to sanitize given address, with memset(0),
> before returning to userland (to avoid leaks of some sort ?).
> 
> With this, the EFAULT is only got when using NULL pointers, and not a
> *bad* pointer??? So, right now, I can???tuse tst_get_bad_addr() like you
> suggested. Should I remove this test and send a v3 ?

Hmm that's strange, memseting the structure does not make much sense.
Cyril Hrubis March 21, 2019, 1:54 p.m. UTC | #3
Hi!
This test fails for me and clock_adjtime() returns 5 (TIME_ERROR) for me.

Looks like the SAFE_ADJTIME() should fail only on rval < 0 and return the rval
too so that we can possibly make use of the return value, the test passes for
me with:

diff --git a/include/tst_safe_clocks.h b/include/tst_safe_clocks.h
index a952be4bf..f7776a548 100644
--- a/include/tst_safe_clocks.h
+++ b/include/tst_safe_clocks.h
@@ -46,16 +46,18 @@ static inline void safe_clock_settime(const char *file, const int lineno,
                        "%s:%d clock_gettime() failed", file, lineno);
 }
 
-static inline void safe_clock_adjtime(const char *file, const int lineno,
+static inline int safe_clock_adjtime(const char *file, const int lineno,
        clockid_t clk_id, struct timex *txc)
 {
        int rval;
 
        rval = tst_syscall(__NR_clock_adjtime, clk_id, txc);
 
-       if (rval != 0)
+       if (rval < 0)
                tst_brk(TBROK | TERRNO,
-                       "%s:%d clock_adjtime() failed", file, lineno);
+                       "%s:%d clock_adjtime() failed %i", file, lineno, rval);
+
+       return rval;
 }


Also we should restore the adjtimex only if we actually managed to save it,
with something as:

diff --git a/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c b/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c
index 48cfbe6c5..6eb823387 100644
--- a/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c
+++ b/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c
@@ -58,6 +58,7 @@
 
 static long hz;
 static struct timex saved, ttxc;
+static int clock_saved;
 
 struct test_case {
        unsigned int modes;
@@ -165,10 +166,13 @@ static void verify_clock_adjtime(unsigned int i)
 static void setup(void)
 {
        size_t i;
+       int rval;
 
        /* save original clock flags */
 
-       SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
+       rval = SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
+       clock_saved = 1;
+       tst_res(TINFO, "clock_adjtime() = %i", rval);
 
        hz = SAFE_SYSCONF(_SC_CLK_TCK);
 
@@ -203,7 +207,8 @@ static void cleanup(void)
 
        /* restore original clock flags */
 
-       SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
+       if (clock_saved)
+               SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
 }
 
 static struct tst_test test = {
Rafael David Tinoco March 21, 2019, 1:57 p.m. UTC | #4
> On 21 Mar 2019, at 10:42, Cyril Hrubis <chrubis@suse.cz> wrote:
> 
> Hi!
>>> +	/*
>>> +	 * special case: EFAULT for NULL pointers
>>> +	 * txcptr = tst_get_bad_addr() -> SIGSEGV is thrown (and not EFAULT)
>>> +	 */
>>> +
>>> +	if (tc[i].exp_err == EFAULT)
>>> +		txcptr = NULL;
>> 
>> Cyril,
>> 
>> clock_adjtime() returns an EFAULT in case copy_from_user() returns
>> errors.
>> 
>> Thing is that, using tst_get_bad_addr(), and having a page with no
>> permissions, before getting an error from copy_from_user() and returning
>> - thus getting EFAULT back - we get a SIGSEGV from _copy_from_user()
>> itself, because it tries to sanitize given address, with memset(0),
>> before returning to userland (to avoid leaks of some sort ?).
>> 
>> With this, the EFAULT is only got when using NULL pointers, and not a
>> *bad* pointer??? So, right now, I can???tuse tst_get_bad_addr() like you
>> suggested. Should I remove this test and send a v3 ?
> 
> Hmm that's strange, memseting the structure does not make much sense.

checking lib/usercopy.c -> _copy_from_user(), memset() is only done in
kernel buffer if there was an error in raw_copy_from_user(), so.. you’re
right. Since mmap’ed buffer has PROT_NONE, I guess the SIGSEGV is thrown
during “raw_copy_from_user()” when referencing the user page with no
permissions. This might not happen when a NULL is passed, making adjtime
to ret EFAULT. Anyway, can’t use tst_get_bad_addr() as it seems...

> -- 
> Cyril Hrubis
> chrubis@suse.cz
Rafael David Tinoco March 21, 2019, 2 p.m. UTC | #5
> On 21 Mar 2019, at 10:54, Cyril Hrubis <chrubis@suse.cz> wrote:
> 
> Hi!
> This test fails for me and clock_adjtime() returns 5 (TIME_ERROR) for me.
> 
> Looks like the SAFE_ADJTIME() should fail only on rval < 0 and return the rval
> too so that we can possibly make use of the return value, the test passes for
> me with:
> 
> diff --git a/include/tst_safe_clocks.h b/include/tst_safe_clocks.h
> index a952be4bf..f7776a548 100644
> --- a/include/tst_safe_clocks.h
> +++ b/include/tst_safe_clocks.h
> @@ -46,16 +46,18 @@ static inline void safe_clock_settime(const char *file, const int lineno,
>                        "%s:%d clock_gettime() failed", file, lineno);
> }
> 
> -static inline void safe_clock_adjtime(const char *file, const int lineno,
> +static inline int safe_clock_adjtime(const char *file, const int lineno,
>        clockid_t clk_id, struct timex *txc)
> {
>        int rval;
> 
>        rval = tst_syscall(__NR_clock_adjtime, clk_id, txc);
> 
> -       if (rval != 0)
> +       if (rval < 0)
>                tst_brk(TBROK | TERRNO,
> -                       "%s:%d clock_adjtime() failed", file, lineno);
> +                       "%s:%d clock_adjtime() failed %i", file, lineno, rval);
> +
> +       return rval;
> }
> 
> 
> Also we should restore the adjtimex only if we actually managed to save it,
> with something as:
> 
> diff --git a/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c b/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c
> index 48cfbe6c5..6eb823387 100644
> --- a/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c
> +++ b/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c
> @@ -58,6 +58,7 @@
> 
> static long hz;
> static struct timex saved, ttxc;
> +static int clock_saved;
> 
> struct test_case {
>        unsigned int modes;
> @@ -165,10 +166,13 @@ static void verify_clock_adjtime(unsigned int i)
> static void setup(void)
> {
>        size_t i;
> +       int rval;
> 
>        /* save original clock flags */
> 
> -       SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
> +       rval = SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
> +       clock_saved = 1;
> +       tst_res(TINFO, "clock_adjtime() = %i", rval);
> 
>        hz = SAFE_SYSCONF(_SC_CLK_TCK);
> 
> @@ -203,7 +207,8 @@ static void cleanup(void)
> 
>        /* restore original clock flags */
> 
> -       SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
> +       if (clock_saved)
> +               SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
> }
> 
> static struct tst_test test = {

Alright, will send a v3, sorry about that.
Cyril Hrubis March 21, 2019, 2:05 p.m. UTC | #6
Hi!
> > Hmm that's strange, memseting the structure does not make much sense.
> 
> checking lib/usercopy.c -> _copy_from_user(), memset() is only done in
> kernel buffer if there was an error in raw_copy_from_user(), so.. you???re
> right. Since mmap???ed buffer has PROT_NONE, I guess the SIGSEGV is thrown
> during ???raw_copy_from_user()??? when referencing the user page with no
> permissions. This might not happen when a NULL is passed, making adjtime
> to ret EFAULT. Anyway, can???t use tst_get_bad_addr() as it seems...

Looking at strace of the test we manage to get -1 and EFAULT followed by
the signal, which is strange. I do wonder why this is the only syscall
that behaves that way.
Cyril Hrubis March 21, 2019, 2:06 p.m. UTC | #7
Hi!
> Alright, will send a v3, sorry about that.

No problem :-) I'm happy to help.
Cyril Hrubis March 21, 2019, 2:10 p.m. UTC | #8
Hi!
> > checking lib/usercopy.c -> _copy_from_user(), memset() is only done in
> > kernel buffer if there was an error in raw_copy_from_user(), so.. you???re
> > right. Since mmap???ed buffer has PROT_NONE, I guess the SIGSEGV is thrown
> > during ???raw_copy_from_user()??? when referencing the user page with no
> > permissions. This might not happen when a NULL is passed, making adjtime
> > to ret EFAULT. Anyway, can???t use tst_get_bad_addr() as it seems...
> 
> Looking at strace of the test we manage to get -1 and EFAULT followed by
> the signal, which is strange. I do wonder why this is the only syscall
> that behaves that way.

Found the bug :-), the kernel is not the problem but rather the lines:

	if (txcptr)
		timex_show("TEST", *txcptr);


You have to change these to something as:

	if (tcxptr != bad_addr)
		timex_show("TEST", *txcptr);
Rafael David Tinoco March 21, 2019, 2:17 p.m. UTC | #9
> On 21 Mar 2019, at 11:10, Cyril Hrubis <chrubis@suse.cz> wrote:
> 
> Hi!
>>> checking lib/usercopy.c -> _copy_from_user(), memset() is only done in
>>> kernel buffer if there was an error in raw_copy_from_user(), so.. you???re
>>> right. Since mmap???ed buffer has PROT_NONE, I guess the SIGSEGV is thrown
>>> during ???raw_copy_from_user()??? when referencing the user page with no
>>> permissions. This might not happen when a NULL is passed, making adjtime
>>> to ret EFAULT. Anyway, can???t use tst_get_bad_addr() as it seems...
>> 
>> Looking at strace of the test we manage to get -1 and EFAULT followed by
>> the signal, which is strange. I do wonder why this is the only syscall
>> that behaves that way.
> 
> Found the bug :-), the kernel is not the problem but rather the lines:
> 
> 	if (txcptr)
> 		timex_show("TEST", *txcptr);
> 
> 
> You have to change these to something as:
> 
> 	if (tcxptr != bad_addr)
> 		timex_show("TEST", *txcptr);

/me blushes for not having opened it in gdb :o)

tks

> 
> -- 
> Cyril Hrubis
> chrubis@suse.cz
diff mbox series

Patch

diff --git a/include/lapi/posix_clocks.h b/include/lapi/posix_clocks.h
index 4914479ac..b1f7a7c55 100644
--- a/include/lapi/posix_clocks.h
+++ b/include/lapi/posix_clocks.h
@@ -11,6 +11,10 @@ 
 
 #define MAX_CLOCKS 16
 
+#define ADJ_ALL (ADJ_OFFSET | ADJ_FREQUENCY | ADJ_MAXERROR |  \
+		 ADJ_ESTERROR | ADJ_STATUS | ADJ_TIMECONST |  \
+		 ADJ_TICK)
+
 #ifndef CLOCK_MONOTONIC_RAW
 # define CLOCK_MONOTONIC_RAW 4
 #endif
diff --git a/runtest/syscalls b/runtest/syscalls
index a13d51918..817f3576b 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -74,6 +74,9 @@  chroot02 chroot02
 chroot03 chroot03
 chroot04 chroot04
 
+clock_adjtime01 clock_adjtime01
+clock_adjtime02 clock_adjtime02
+
 clock_getres01 clock_getres01
 clock_nanosleep01 clock_nanosleep01
 clock_nanosleep02 clock_nanosleep02
diff --git a/testcases/kernel/syscalls/clock_adjtime/.gitignore b/testcases/kernel/syscalls/clock_adjtime/.gitignore
new file mode 100644
index 000000000..28d5a1d45
--- /dev/null
+++ b/testcases/kernel/syscalls/clock_adjtime/.gitignore
@@ -0,0 +1,2 @@ 
+clock_adjtime01
+clock_adjtime02
diff --git a/testcases/kernel/syscalls/clock_adjtime/Makefile b/testcases/kernel/syscalls/clock_adjtime/Makefile
new file mode 100644
index 000000000..79f671f1c
--- /dev/null
+++ b/testcases/kernel/syscalls/clock_adjtime/Makefile
@@ -0,0 +1,10 @@ 
+# Copyright (c) 2019 - Linaro Limited. All rights reserved.
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+top_srcdir		?= ../../../..
+
+include $(top_srcdir)/include/mk/testcases.mk
+
+LDLIBS+=-lrt
+
+include $(top_srcdir)/include/mk/generic_leaf_target.mk
\ No newline at end of file
diff --git a/testcases/kernel/syscalls/clock_adjtime/clock_adjtime.h b/testcases/kernel/syscalls/clock_adjtime/clock_adjtime.h
new file mode 100644
index 000000000..34b97fdfa
--- /dev/null
+++ b/testcases/kernel/syscalls/clock_adjtime/clock_adjtime.h
@@ -0,0 +1,57 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2019 Linaro Limited. All rights reserved.
+ * Author: Rafael David Tinoco <rafael.tinoco@linaro.org>
+ */
+
+#include "config.h"
+#include "tst_test.h"
+#include "tst_timer.h"
+#include "tst_safe_clocks.h"
+#include "lapi/syscalls.h"
+#include "lapi/posix_clocks.h"
+#include <time.h>
+#include <pwd.h>
+#include <sys/timex.h>
+#include <sys/types.h>
+
+static int sys_clock_adjtime(clockid_t, struct timex *);
+static void timex_show(char *, struct timex);
+
+/*
+ * bad pointer w/ libc causes SIGSEGV signal, call syscall directly
+ */
+static int sys_clock_adjtime(clockid_t clk_id, struct timex *txc)
+{
+	return tst_syscall(__NR_clock_adjtime, clk_id, txc);
+}
+
+static void timex_show(char *given, struct timex txc)
+{
+	tst_res(TINFO,  "%s\n"
+			"             mode: %d\n"
+			"           offset: %ld\n"
+			"        frequency: %ld\n"
+			"         maxerror: %ld\n"
+			"         esterror: %ld\n"
+			"           status: %d (0x%x)\n"
+			"    time_constant: %ld\n"
+			"        precision: %ld\n"
+			"        tolerance: %ld\n"
+			"             tick: %ld\n"
+			"         raw time: %d(s) %d(us)",
+			given,
+			txc.modes,
+			txc.offset,
+			txc.freq,
+			txc.maxerror,
+			txc.esterror,
+			txc.status,
+			txc.status,
+			txc.constant,
+			txc.precision,
+			txc.tolerance,
+			txc.tick,
+			(int)txc.time.tv_sec,
+			(int)txc.time.tv_usec);
+}
diff --git a/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c b/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c
new file mode 100644
index 000000000..48cfbe6c5
--- /dev/null
+++ b/testcases/kernel/syscalls/clock_adjtime/clock_adjtime01.c
@@ -0,0 +1,216 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2019 Linaro Limited. All rights reserved.
+ * Author: Rafael David Tinoco <rafael.tinoco@linaro.org>
+ */
+
+/*
+ * clock_adjtime() syscall might have as execution path:
+ *
+ *   1) a regular POSIX clock (only REALTIME clock implements adjtime())
+ *      - will behave exactly like adjtimex() system call.
+ *      - only one being tested here.
+ *
+ *   2) a dynamic POSIX clock (which ops are implemented by PTP clocks)
+ *      - will trigger the PTP clock driver function "adjtime()"
+ *      - different implementations from one PTP clock to another
+ *      - might return EOPNOTSUPP (like ptp_kvm_caps, for example)
+ *      - no entry point for clock_adjtime(), missing "CLOCK_PTP" model
+ *
+ * so it is sane to check possible adjustments:
+ *
+ *    - ADJ_OFFSET     - usec or nsec, kernel adjusts time gradually by offset
+ *                       (-512000 < offset < 512000)
+ *    - ADJ_FREQUENCY  - system clock frequency offset
+ *    - ADJ_MAXERROR   - maximum error (usec)
+ *    - ADJ_ESTERROR   - estimated time error in us
+ *    - ADJ_STATUS     - clock command/status of ntp implementation
+ *    - ADJ_TIMECONST  - PLL stiffness (jitter dependent) + poll int for PLL
+ *    - ADJ_TICK       - us between clock ticks
+ *                       (>= 900000/HZ, <= 1100000/HZ)
+ *
+ * and also the standalone ones (using .offset variable):
+ *
+ *    - ADJ_OFFSET_SINGLESHOT - behave like adjtime()
+ *    - ADJ_OFFSET_SS_READ - ret remaining time for completion after SINGLESHOT
+ *
+ * For ADJ_STATUS, consider the following flags:
+ *
+ *      rw  STA_PLL - enable phase-locked loop updates (ADJ_OFFSET)
+ *      rw  STA_PPSFREQ - enable PPS (pulse-per-second) freq discipline
+ *      rw  STA_PPSTIME - enable PPS time discipline
+ *      rw  STA_FLL - select freq-locked loop mode.
+ *      rw  STA_INS - ins leap sec after the last sec of UTC day (all days)
+ *      rw  STA_DEL - del leap sec at last sec of UTC day (all days)
+ *      rw  STA_UNSYNC - clock unsynced
+ *      rw  STA_FREQHOLD - hold freq. ADJ_OFFSET made w/out auto small adjs
+ *      ro  STA_PPSSIGNAL - valid PPS (pulse-per-second) signal is present
+ *      ro  STA_PPSJITTER - PPS signal jitter exceeded.
+ *      ro  STA_PPSWANDER - PPS signal wander exceeded.
+ *      ro  STA_PPSERROR - PPS signal calibration error.
+ *      ro  STA_CLOKERR - clock HW fault.
+ *      ro  STA_NANO - 0 = us, 1 = ns (set = ADJ_NANO, cl = ADJ_MICRO)
+ *      rw  STA_MODE - mode: 0 = phased locked loop. 1 = freq locked loop
+ *      ro  STA_CLK - clock source. unused.
+ */
+
+#include "clock_adjtime.h"
+
+static long hz;
+static struct timex saved, ttxc;
+
+struct test_case {
+	unsigned int modes;
+	long highlimit;
+	long *ptr;
+	long delta;
+};
+
+struct test_case tc[] = {
+	{
+	 .modes = ADJ_OFFSET_SINGLESHOT,
+	},
+	{
+	 .modes = ADJ_OFFSET_SS_READ,
+	},
+	{
+	 .modes = ADJ_ALL,
+	},
+	{
+	 .modes = ADJ_OFFSET,
+	 .highlimit = 500000,
+	 .ptr = &ttxc.offset,
+	 .delta = 10000,
+	},
+	{
+	 .modes = ADJ_FREQUENCY,
+	 .ptr = &ttxc.freq,
+	 .delta = 100,
+	},
+	{
+	 .modes = ADJ_MAXERROR,
+	 .ptr = &ttxc.maxerror,
+	 .delta = 100,
+	},
+	{
+	 .modes = ADJ_ESTERROR,
+	 .ptr = &ttxc.esterror,
+	 .delta = 100,
+	},
+	{
+	 .modes = ADJ_TIMECONST,
+	 .ptr = &ttxc.constant,
+	 .delta = 1,
+	},
+	{
+	 .modes = ADJ_TICK,
+	 .highlimit = 1100000,
+	 .ptr = &ttxc.tick,
+	 .delta = 1000,
+	},
+};
+
+static void verify_clock_adjtime(unsigned int i)
+{
+	long ptroff, *ptr = NULL;
+	struct timex verify;
+
+	memset(&ttxc, 0, sizeof(struct timex));
+	memset(&verify, 0, sizeof(struct timex));
+
+	SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &ttxc);
+	timex_show("GET", ttxc);
+
+	ttxc.modes = tc[i].modes;
+
+	if (tc[i].ptr && tc[i].delta) {
+
+		*tc[i].ptr += tc[i].delta;
+
+		/* fix limits, if existent, so no errors occur */
+
+		if (tc[i].highlimit) {
+			if (*tc[i].ptr >= tc[i].highlimit)
+				*tc[i].ptr = tc[i].highlimit;
+		}
+	}
+
+	SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &ttxc);
+	timex_show("SET", ttxc);
+
+	if (tc[i].ptr) {
+
+		/* adjtimex field being tested so we can verify later */
+
+		ptroff = (long) tc[i].ptr - (long) &ttxc;
+		ptr = (void *) &verify + ptroff;
+	}
+
+	TEST(sys_clock_adjtime(CLOCK_REALTIME, &verify));
+	timex_show("VERIFY", verify);
+
+	if (tc[i].ptr && *tc[i].ptr != *ptr) {
+		tst_res(TFAIL, "clock_adjtime(): could not set value (mode=%x)",
+				tc[i].modes);
+	}
+
+	if (TST_RET < 0) {
+		tst_res(TFAIL | TTERRNO, "clock_adjtime(): mode=%x, returned "
+				"error", tc[i].modes);
+	}
+
+	tst_res(TPASS, "clock_adjtime(): success (mode=%x)", tc[i].modes);
+}
+
+static void setup(void)
+{
+	size_t i;
+
+	/* save original clock flags */
+
+	SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
+
+	hz = SAFE_SYSCONF(_SC_CLK_TCK);
+
+	for (i = 0; i < ARRAY_SIZE(tc); i++) {
+
+		/* fix high and low limits by dividing it per HZ value */
+
+		if (tc[i].modes == ADJ_TICK)
+			tc[i].highlimit /= hz;
+
+		/* fix usec as being test default resolution */
+
+		if (saved.modes & ADJ_NANO) {
+			if (tc[i].modes == ADJ_OFFSET) {
+				tc[i].highlimit *= 1000;
+				tc[i].delta *= 1000;
+			}
+		}
+	}
+}
+
+static void cleanup(void)
+{
+	saved.modes = ADJ_ALL;
+
+	/* restore clock resolution based on original status flag */
+
+	if (saved.status & STA_NANO)
+		saved.modes |= ADJ_NANO;
+	else
+		saved.modes |= ADJ_MICRO;
+
+	/* restore original clock flags */
+
+	SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
+}
+
+static struct tst_test test = {
+	.test = verify_clock_adjtime,
+	.setup = setup,
+	.cleanup = cleanup,
+	.tcnt = ARRAY_SIZE(tc),
+	.needs_root = 1,
+	.restore_wallclock = 1,
+};
diff --git a/testcases/kernel/syscalls/clock_adjtime/clock_adjtime02.c b/testcases/kernel/syscalls/clock_adjtime/clock_adjtime02.c
new file mode 100644
index 000000000..de9bae860
--- /dev/null
+++ b/testcases/kernel/syscalls/clock_adjtime/clock_adjtime02.c
@@ -0,0 +1,218 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2019 Linaro Limited. All rights reserved.
+ * Author: Rafael David Tinoco <rafael.tinoco@linaro.org>
+ */
+
+/*
+ * clock_adjtime() syscall might have as execution path:
+ *
+ *   1) a regular POSIX clock (only REALTIME clock implements adjtime())
+ *      - will behave exactly like adjtimex() system call.
+ *      - only one being tested here.
+ *
+ *   2) a dynamic POSIX clock (which ops are implemented by PTP clocks)
+ *      - will trigger the PTP clock driver function "adjtime()"
+ *      - different implementations from one PTP clock to another
+ *      - might return EOPNOTSUPP (like ptp_kvm_caps, for example)
+ *      - no entry point for clock_adjtime(), missing "CLOCK_PTP" model
+ *
+ * so it is sane to check for the following errors:
+ *
+ *   EINVAL -  clock id being used does not exist
+ *
+ *   EFAULT - (struct timex *) does not point to valid memory
+ *
+ *   EINVAL - ADJ_OFFSET + .offset outside range -512000 < x < 512000
+ *            (after 2.6.26, kernels normalize to the limit if outside range)
+ *
+ *   EINVAL - ADJ_FREQUENCY + .freq outside range -32768000 < x < 3276800
+ *            (after 2.6.26, kernels normalize to the limit if outside range)
+ *
+ *   EINVAL - .tick outside permitted range (900000/HZ < .tick < 1100000/HZ)
+ *
+ *   EPERM  - .modes is neither 0 nor ADJ_OFFSET_SS_READ (CAP_SYS_TIME required)
+ *
+ *   EINVAL - .status other than those listed bellow
+ *
+ * For ADJ_STATUS, consider the following flags:
+ *
+ *      rw  STA_PLL - enable phase-locked loop updates (ADJ_OFFSET)
+ *      rw  STA_PPSFREQ - enable PPS (pulse-per-second) freq discipline
+ *      rw  STA_PPSTIME - enable PPS time discipline
+ *      rw  STA_FLL - select freq-locked loop mode.
+ *      rw  STA_INS - ins leap sec after the last sec of UTC day (all days)
+ *      rw  STA_DEL - del leap sec at last sec of UTC day (all days)
+ *      rw  STA_UNSYNC - clock unsynced
+ *      rw  STA_FREQHOLD - hold freq. ADJ_OFFSET made w/out auto small adjs
+ *      ro  STA_PPSSIGNAL - valid PPS (pulse-per-second) signal is present
+ *      ro  STA_PPSJITTER - PPS signal jitter exceeded.
+ *      ro  STA_PPSWANDER - PPS signal wander exceeded.
+ *      ro  STA_PPSERROR - PPS signal calibration error.
+ *      ro  STA_CLOKERR - clock HW fault.
+ *      ro  STA_NANO - 0 = us, 1 = ns (set = ADJ_NANO, cl = ADJ_MICRO)
+ *      rw  STA_MODE - mode: 0 = phased locked loop. 1 = freq locked loop
+ *      ro  STA_CLK - clock source. unused.
+ */
+
+#include "clock_adjtime.h"
+
+static long hz;
+static struct timex saved, ttxc;
+
+static void cleanup(void);
+
+struct test_case {
+	clockid_t clktype;
+	unsigned int modes;
+	long lowlimit;
+	long highlimit;
+	long *ptr;
+	long delta;
+	int exp_err;
+	int droproot;
+};
+
+struct test_case tc[] = {
+	{
+	 .clktype = MAX_CLOCKS,
+	 .exp_err = EINVAL,
+	},
+	{
+	 .clktype = MAX_CLOCKS + 1,
+	 .exp_err = EINVAL,
+	},
+	{
+	 .clktype = CLOCK_REALTIME,
+	 .modes = ADJ_ALL,
+	 .exp_err = EFAULT,
+	},
+	{
+	 .clktype = CLOCK_REALTIME,
+	 .modes = ADJ_TICK,
+	 .lowlimit = 900000,
+	 .ptr = &ttxc.tick,
+	 .delta = 1,
+	 .exp_err = EINVAL,
+	},
+	{
+	 .clktype = CLOCK_REALTIME,
+	 .modes = ADJ_TICK,
+	 .highlimit = 1100000,
+	 .ptr = &ttxc.tick,
+	 .delta = 1,
+	 .exp_err = EINVAL,
+	},
+	{
+	 .clktype = CLOCK_REALTIME,
+	 .modes = ADJ_ALL,
+	 .exp_err = EPERM,
+	 .droproot = 1,
+	},
+};
+
+static void verify_clock_adjtime(unsigned int i)
+{
+	uid_t whoami = 0;
+	struct timex *txcptr;
+	struct passwd *nobody;
+	static const char name[] = "nobody";
+
+	txcptr = &ttxc;
+
+	memset(txcptr, 0, sizeof(struct timex));
+
+	SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, txcptr);
+	timex_show("GET", *txcptr);
+
+	if (tc[i].droproot) {
+		nobody = SAFE_GETPWNAM(name);
+		whoami = nobody->pw_uid;
+		SAFE_SETEUID(whoami);
+	}
+
+	txcptr->modes = tc[i].modes;
+
+	if (tc[i].ptr) {
+
+		if (tc[i].lowlimit)
+			*tc[i].ptr = tc[i].lowlimit - tc[i].delta;
+
+		if (tc[i].highlimit)
+			*tc[i].ptr = tc[i].highlimit + tc[i].delta;
+	}
+
+	/*
+	 * special case: EFAULT for NULL pointers
+	 * txcptr = tst_get_bad_addr() -> SIGSEGV is thrown (and not EFAULT)
+	 */
+
+	if (tc[i].exp_err == EFAULT)
+		txcptr = NULL;
+
+	TEST(sys_clock_adjtime(tc[i].clktype, txcptr));
+	if (txcptr)
+		timex_show("TEST", *txcptr);
+
+	if (TST_RET >= 0) {
+		tst_res(TFAIL, "clock_adjtime(): passed unexpectedly (mode=%x, "
+				"uid=%d)", tc[i].modes, whoami);
+		return;
+	}
+
+	if (tc[i].exp_err != TST_ERR) {
+		tst_res(TFAIL | TTERRNO, "clock_adjtime(): expected %d but "
+				"failed with %d (mode=%x, uid=%d)",
+				tc[i].exp_err, TST_ERR, tc[i].modes, whoami);
+		return;
+	}
+
+	tst_res(TPASS, "clock_adjtime(): failed as expected (mode=0x%x, "
+			"uid=%d)", tc[i].modes, whoami);
+
+	if (tc[i].droproot)
+		SAFE_SETEUID(0);
+}
+
+static void setup(void)
+{
+	size_t i;
+
+	hz = SAFE_SYSCONF(_SC_CLK_TCK);
+
+	/* fix high and low limits by dividing it per HZ value */
+
+	for (i = 0; i < ARRAY_SIZE(tc); i++) {
+		if (tc[i].modes == ADJ_TICK) {
+			tc[i].highlimit /= hz;
+			tc[i].lowlimit /= hz;
+		}
+	}
+
+	SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
+}
+
+static void cleanup(void)
+{
+	saved.modes = ADJ_ALL;
+
+	/* restore clock resolution based on original status flag */
+
+	if (saved.status & STA_NANO)
+		saved.modes |= ADJ_NANO;
+	else
+		saved.modes |= ADJ_MICRO;
+
+	/* restore original clock flags */
+
+	SAFE_CLOCK_ADJTIME(CLOCK_REALTIME, &saved);
+}
+
+static struct tst_test test = {
+	.test = verify_clock_adjtime,
+	.setup = setup,
+	.cleanup = cleanup,
+	.tcnt = ARRAY_SIZE(tc),
+	.needs_root = 1,
+	.restore_wallclock = 1,
+};