When wall_to_monotonic become positive, then I get below two
errors on an IMX6 development board without enable RTC device:

1:execute exportfs -a generate:
   "exportfs: /opt/nfs/arm does not support NFS export"
2:cat /proc/stat:
   "btime 4294967236"

I can product the same issues on x86 with newest kernel in next
tree with below c code:
        "
        int main(void)
        {
            struct timeval val;
            int ret;

            val.tv_sec = 0;
            val.tv_usec = 0;
            ret = settimeofday(&val, NULL);
            printf("ret:%d\n", ret);
            return 0;
        }
        "

The reason is getboottime[64] return negative value, cause below
codes don't work:

nfs error:cache.h:get_expiry return negative value cause
          cache_flush always clear entries just added in
          ip_map_parse.
proc/stat error:
          seq_printf use "unsigned long" to show
          a negative number return by getboottime.

This patch fix it by validate new value of wall_to_monotonic
before assign it.

Signed-off-by: Wang YanQing <udkni...@gmail.com>
---
 kernel/time/timekeeping.c | 71 ++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 55 insertions(+), 16 deletions(-)

diff --git a/kernel/time/timekeeping.c b/kernel/time/timekeeping.c
index 91db941..799e323 100644
--- a/kernel/time/timekeeping.c
+++ b/kernel/time/timekeeping.c
@@ -96,11 +96,29 @@ static void tk_xtime_add(struct timekeeper *tk, const 
struct timespec64 *ts)
        tk_normalize_xtime(tk);
 }
 
-static void tk_set_wall_to_mono(struct timekeeper *tk, struct timespec64 wtm)
+static inline bool timespec64_negative(struct timespec64 *ts)
+{
+       if (ts->tv_sec > 0)
+               return false;
+       if (ts->tv_sec == 0 && ts->tv_nsec > 0)
+               return false;
+       return true;
+}
+
+static bool tk_set_wall_to_mono(struct timekeeper *tk, struct timespec64 wtm)
 {
        struct timespec64 tmp;
 
        /*
+        * The current time
+        * wall_to_monotonic is what we need to add to xtime (or xtime corrected
+        * for sub jiffie times) to get to monotonic time.  Monotonic is pegged
+        * at zero at system boot time, so wall_to_monotonic will be negative.
+        */
+       if (!timespec64_negative(&wtm))
+               return false;
+
+       /*
         * Verify consistency of: offset_real = -wall_to_monotonic
         * before modifying anything
         */
@@ -111,6 +129,7 @@ static void tk_set_wall_to_mono(struct timekeeper *tk, 
struct timespec64 wtm)
        set_normalized_timespec64(&tmp, -wtm.tv_sec, -wtm.tv_nsec);
        tk->offs_real = timespec64_to_ktime(tmp);
        tk->offs_tai = ktime_add(tk->offs_real, ktime_set(tk->tai_offset, 0));
+       return true;
 }
 
 static inline void tk_update_sleep_time(struct timekeeper *tk, ktime_t delta)
@@ -796,8 +815,9 @@ EXPORT_SYMBOL(do_gettimeofday);
 int do_settimeofday64(const struct timespec64 *ts)
 {
        struct timekeeper *tk = &tk_core.timekeeper;
-       struct timespec64 ts_delta, xt;
+       struct timespec64 ts_delta, tmp;
        unsigned long flags;
+       int ret = 0;
 
        if (!timespec64_valid_strict(ts))
                return -EINVAL;
@@ -807,23 +827,27 @@ int do_settimeofday64(const struct timespec64 *ts)
 
        timekeeping_forward_now(tk);
 
-       xt = tk_xtime(tk);
-       ts_delta.tv_sec = ts->tv_sec - xt.tv_sec;
-       ts_delta.tv_nsec = ts->tv_nsec - xt.tv_nsec;
+       tmp = tk_xtime(tk);
+       ts_delta.tv_sec = ts->tv_sec - tmp.tv_sec;
+       ts_delta.tv_nsec = ts->tv_nsec - tmp.tv_nsec;
 
-       tk_set_wall_to_mono(tk, timespec64_sub(tk->wall_to_monotonic, 
ts_delta));
+       tmp = timespec64_sub(tk->wall_to_monotonic, ts_delta);
+       if (!tk_set_wall_to_mono(tk, tmp)) {
+               ret = -EINVAL;
+               goto out;
+       }
 
        tk_set_xtime(tk, ts);
-
        timekeeping_update(tk, TK_CLEAR_NTP | TK_MIRROR | TK_CLOCK_WAS_SET);
-
+out:
        write_seqcount_end(&tk_core.seq);
        raw_spin_unlock_irqrestore(&timekeeper_lock, flags);
 
        /* signal hrtimers about time change */
-       clock_was_set();
+       if (!ret)
+               clock_was_set();
 
-       return 0;
+       return ret;
 }
 EXPORT_SYMBOL(do_settimeofday64);
 
@@ -857,8 +881,13 @@ int timekeeping_inject_offset(struct timespec *ts)
                goto error;
        }
 
+       tmp = timespec64_sub(tk->wall_to_monotonic, ts64);
+       if (!tk_set_wall_to_mono(tk, tmp)) {
+               ret = -EINVAL;
+               goto error;
+       }
+
        tk_xtime_add(tk, &ts64);
-       tk_set_wall_to_mono(tk, timespec64_sub(tk->wall_to_monotonic, ts64));
 
 error: /* even if we error out, we forwarded the time, so call update */
        timekeeping_update(tk, TK_CLEAR_NTP | TK_MIRROR | TK_CLOCK_WAS_SET);
@@ -1140,14 +1169,21 @@ static struct timespec64 timekeeping_suspend_time;
 static void __timekeeping_inject_sleeptime(struct timekeeper *tk,
                                           struct timespec64 *delta)
 {
+       struct timespec64 tmp;
+
        if (!timespec64_valid_strict(delta)) {
                printk_deferred(KERN_WARNING
                                "__timekeeping_inject_sleeptime: Invalid "
                                "sleep delta value!\n");
                return;
        }
+
+       tmp = timespec64_sub(tk->wall_to_monotonic, *delta);
+       if (!tk_set_wall_to_mono(tk, tmp)) {
+               WARN_ON(1);
+               return;
+       }
        tk_xtime_add(tk, delta);
-       tk_set_wall_to_mono(tk, timespec64_sub(tk->wall_to_monotonic, *delta));
        tk_update_sleep_time(tk, timespec64_to_ktime(*delta));
        tk_debug_account_sleep_time(delta);
 }
@@ -1539,14 +1575,17 @@ static inline unsigned int 
accumulate_nsecs_to_secs(struct timekeeper *tk)
                leap = second_overflow(tk->xtime_sec);
                if (unlikely(leap)) {
                        struct timespec64 ts;
-
-                       tk->xtime_sec += leap;
+                       bool ret;
 
                        ts.tv_sec = leap;
                        ts.tv_nsec = 0;
-                       tk_set_wall_to_mono(tk,
+                       ret = tk_set_wall_to_mono(tk,
                                timespec64_sub(tk->wall_to_monotonic, ts));
-
+                       if (!ret) {
+                               WARN_ON_ONCE(1);
+                               break;
+                       }
+                       tk->xtime_sec += leap;
                        __timekeeping_set_tai_offset(tk, tk->tai_offset - leap);
 
                        clock_set = TK_CLOCK_WAS_SET;
-- 
2.2.2.dirty
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to