linux Truncation error when converting unsigned long long to long

Essentially, the problem is that we try to convert a 64-bit integer to a 32-bit one. Variable `time_delta' has type u64 which is always 64-bit wide. It holds a value in nanoseconds that is converted to seconds and stored in a variable of type long. In architectures where long is 32-bit wide, time_delta/NSEC_PER_SEC may not fit if time_delta is large enough, and we end up storing a negative number of seconds. As a consequence, if the computed expiration time is negative, this causes an infinite loop.
Bug fixed by commit 51fd36f3fad
Type NumericTruncation
Config "!64BIT && !KTIME_SCALAR" (2nd degree)
Fix-in code
Location kernel/
#include <limits.h>
#include <stdint.h>

typedef int32_t  s32;
typedef int64_t  s64;
typedef uint64_t u64;

#ifdef CONFIG_64BIT
#define BITS_PER_LONG 64
#else
#define BITS_PER_LONG 32
#endif /* CONFIG_64BIT */

#define NSEC_PER_SEC	1000000000L

#define KTIME_MAX			((s64)~((u64)1 << 63))
#if (BITS_PER_LONG == 64)
#define KTIME_SEC_MAX			(KTIME_MAX / NSEC_PER_SEC)
#else
#define KTIME_SEC_MAX			LONG_MAX
#endif

#if (BITS_PER_LONG == 64) || defined(CONFIG_KTIME_SCALAR)

#define ktime_add_ns(nsval) (nsval)

#else	/* !((BITS_PER_LONG == 64) || defined(CONFIG_KTIME_SCALAR)) */

static inline s64 ktime_set(const long secs)
{
  return (s64)secs * NSEC_PER_SEC;
}

s64 ktime_add_ns(u64 nsec)
{
  s64 tmp;

  if (nsec < NSEC_PER_SEC) {
    tmp = nsec;
  } else {
    unsigned long sec = nsec / NSEC_PER_SEC;
    tmp = ktime_set((s32)sec); // ERROR
  }

  return tmp;
}

#endif

static u64 tick_nohz_stop_sched_tick()
{
  u64 time_delta =(u64) 7881299347898368000;
  s64 expires;

  if (time_delta < KTIME_MAX)
    expires = ktime_add_ns(time_delta);
  else
    expires = KTIME_MAX;
    
  return expires;
}

int main(int argc, char** argv)
{
  tick_nohz_stop_sched_tick();
  return 0;
}

diff --git a/simple/51fd36f.c b/simple/51fd36f.c
--- a/simple/51fd36f.c
+++ b/simple/51fd36f.c
@@ -40,6 +40,9 @@
     tmp = nsec;
   } else {
     unsigned long sec = nsec / NSEC_PER_SEC;
+    /* Make sure nsec fits into long */
+    if (nsec > KTIME_SEC_MAX)
+        return KTIME_MAX;
     tmp = ktime_set((s32)sec); // ERROR
   }
 
#include <limits.h>
#include <stdint.h>

typedef int32_t  s32;
typedef int64_t  s64;
typedef uint64_t u64;

#ifdef CONFIG_64BIT
#define BITS_PER_LONG 64
#else
#define BITS_PER_LONG 32
#endif /* CONFIG_64BIT */

#define NSEC_PER_SEC	1000000000L

#define KTIME_MAX			((s64)~((u64)1 << 63))
#if (BITS_PER_LONG == 64)
#define KTIME_SEC_MAX			(KTIME_MAX / NSEC_PER_SEC)
#else
#define KTIME_SEC_MAX			LONG_MAX
#endif

int main(int argc, char** argv)
{
//  tick_nohz_stop_sched_tick();
  u64 time_delta =(u64) 7881299347898368000;
  s64 expires;

  if (time_delta < KTIME_MAX) {
    //expires = ktime_add_ns(time_delta);
    #if defined(BITS_PER_LONG == 64) || defined(CONFIG_KTIME_SCALAR)
    expires = time_delta;
    #else
    s64 tmp;

    if (nsec < NSEC_PER_SEC) {
      tmp = nsec;
    } else {
      unsigned long sec = nsec / NSEC_PER_SEC;
      tmp = (s64)((s32)sec) * NSEC_PER_SEC; // ERROR
    }

    expires = tmp;
    #endif
  }
  else
    expires = KTIME_MAX;
    
  return 0;
}

. call kernel/time/tick-sched.c:318:tick_nohz_stop_sched_tick()
. 325: u64 time_delta;
. 332: time_delta = timekeeping_max_deferment();
. 400: if (time_delta < KTIME_MAX)
. 401: expires = ktime_add_ns(last_update, time_delta);
.. call kernel/hrtimer.c:280:ktime_add_ns()
.. 286: else
.. 287: unsigned long rem = do_div(nsec, NSEC_PER_SEC);
// sets nsec to nsec/NSEC_PER_SEC
.. ERROR 289: tmp = ktime_set((long)nsec, rem);
// (long)nsec overflows