• Michael Ellerman's avatar
    powerpc/vdso64: Fix CLOCK_MONOTONIC inconsistencies across Y2038 · b5b4453e
    Michael Ellerman authored
    Jakub Drnec reported:
      Setting the realtime clock can sometimes make the monotonic clock go
      back by over a hundred years. Decreasing the realtime clock across
      the y2k38 threshold is one reliable way to reproduce. Allegedly this
      can also happen just by running ntpd, I have not managed to
      reproduce that other than booting with rtc at >2038 and then running
      ntp. When this happens, anything with timers (e.g. openjdk) breaks
      rather badly.
    
    And included a test case (slightly edited for brevity):
      #define _POSIX_C_SOURCE 199309L
      #include <stdio.h>
      #include <time.h>
      #include <stdlib.h>
      #include <unistd.h>
    
      long get_time(void) {
        struct timespec tp;
        clock_gettime(CLOCK_MONOTONIC, &tp);
        return tp.tv_sec + tp.tv_nsec / 1000000000;
      }
    
      int main(void) {
        long last = get_time();
        while(1) {
          long now = get_time();
          if (now < last) {
            printf("clock went backwards by %ld seconds!\n", last - now);
          }
          last = now;
          sleep(1);
        }
        return 0;
      }
    
    Which when run concurrently with:
     # date -s 2040-1-1
     # date -s 2037-1-1
    
    Will detect the clock going backward.
    
    The root cause is that wtom_clock_sec in struct vdso_data is only a
    32-bit signed value, even though we set its value to be equal to
    tk->wall_to_monotonic.tv_sec which is 64-bits.
    
    Because the monotonic clock starts at zero when the system boots the
    wall_to_montonic.tv_sec offset is negative for current and future
    dates. Currently on a freshly booted system the offset will be in the
    vicinity of negative 1.5 billion seconds.
    
    However if the wall clock is set past the Y2038 boundary, the offset
    from wall to monotonic becomes less than negative 2^31, and no longer
    fits in 32-bits. When that value is assigned to wtom_clock_sec it is
    truncated and becomes positive, causing the VDSO assembly code to
    calculate CLOCK_MONOTONIC incorrectly.
    
    That causes CLOCK_MONOTONIC to jump ahead by ~4 billion seconds which
    it is not meant to do. Worse, if the time is then set back before the
    Y2038 boundary CLOCK_MONOTONIC will jump backward.
    
    We can fix it simply by storing the full 64-bit offset in the
    vdso_data, and using that in the VDSO assembly code. We also shuffle
    some of the fields in vdso_data to avoid creating a hole.
    
    The original commit that added the CLOCK_MONOTONIC support to the VDSO
    did actually use a 64-bit value for wtom_clock_sec, see commit
    a7f290da ("[PATCH] powerpc: Merge vdso's and add vdso support to
    32 bits kernel") (Nov 2005). However just 3 days later it was
    converted to 32-bits in commit 0c37ec2a ("[PATCH] powerpc: vdso
    fixes (take #2)"), and the bug has existed since then AFAICS.
    
    Fixes: 0c37ec2a ("[PATCH] powerpc: vdso fixes (take #2)")
    Cc: stable@vger.kernel.org # v2.6.15+
    Link: http://lkml.kernel.org/r/HaC.ZfES.62bwlnvAvMP.1STMMj@seznam.czReported-by: default avatarJakub Drnec <jaydee@email.cz>
    Signed-off-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
    b5b4453e
vdso_datapage.h 4.31 KB