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