![John Wright](/assets/img/avatar_default.png)
According to the gettimeofday(2) manual: If either tv or tz is NULL, the corresponding structure is not set or returned. Since it is legal to give NULL as the tv argument, the code should make sure tv is not NULL before trying to dereference it. This issue manifests itself on x86_64 when vdso=0 is not on the kernel command-line and libc uses the vDSO for gettimeofday() (e.g. glibc >= 2.7). A simple reproducer: #include <stdio.h> #include <sys/time.h> int main(void) { struct timezone tz; gettimeofday(NULL, &tz); return 0; } See http://bugs.debian.org/466491 for more details. [ Impact: fix gettimeofday(NULL, &tz) segfault ] Signed-off-by: John Wright <john.wright@hp.com> Cc: Andi Kleen <ak@suse.de> Cc: John Wright <john.wright@hp.com> LKML-Reference: <1241037121-14805-1-git-send-email-john.wright@hp.com> Signed-off-by: Ingo Molnar <mingo@elte.hu>
127 lines
3.3 KiB
C
127 lines
3.3 KiB
C
/*
|
|
* Copyright 2006 Andi Kleen, SUSE Labs.
|
|
* Subject to the GNU Public License, v.2
|
|
*
|
|
* Fast user context implementation of clock_gettime and gettimeofday.
|
|
*
|
|
* The code should have no internal unresolved relocations.
|
|
* Check with readelf after changing.
|
|
* Also alternative() doesn't work.
|
|
*/
|
|
|
|
/* Disable profiling for userspace code: */
|
|
#define DISABLE_BRANCH_PROFILING
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/posix-timers.h>
|
|
#include <linux/time.h>
|
|
#include <linux/string.h>
|
|
#include <asm/vsyscall.h>
|
|
#include <asm/vgtod.h>
|
|
#include <asm/timex.h>
|
|
#include <asm/hpet.h>
|
|
#include <asm/unistd.h>
|
|
#include <asm/io.h>
|
|
#include "vextern.h"
|
|
|
|
#define gtod vdso_vsyscall_gtod_data
|
|
|
|
notrace static long vdso_fallback_gettime(long clock, struct timespec *ts)
|
|
{
|
|
long ret;
|
|
asm("syscall" : "=a" (ret) :
|
|
"0" (__NR_clock_gettime),"D" (clock), "S" (ts) : "memory");
|
|
return ret;
|
|
}
|
|
|
|
notrace static inline long vgetns(void)
|
|
{
|
|
long v;
|
|
cycles_t (*vread)(void);
|
|
vread = gtod->clock.vread;
|
|
v = (vread() - gtod->clock.cycle_last) & gtod->clock.mask;
|
|
return (v * gtod->clock.mult) >> gtod->clock.shift;
|
|
}
|
|
|
|
notrace static noinline int do_realtime(struct timespec *ts)
|
|
{
|
|
unsigned long seq, ns;
|
|
do {
|
|
seq = read_seqbegin(>od->lock);
|
|
ts->tv_sec = gtod->wall_time_sec;
|
|
ts->tv_nsec = gtod->wall_time_nsec;
|
|
ns = vgetns();
|
|
} while (unlikely(read_seqretry(>od->lock, seq)));
|
|
timespec_add_ns(ts, ns);
|
|
return 0;
|
|
}
|
|
|
|
/* Copy of the version in kernel/time.c which we cannot directly access */
|
|
notrace static void
|
|
vset_normalized_timespec(struct timespec *ts, long sec, long nsec)
|
|
{
|
|
while (nsec >= NSEC_PER_SEC) {
|
|
nsec -= NSEC_PER_SEC;
|
|
++sec;
|
|
}
|
|
while (nsec < 0) {
|
|
nsec += NSEC_PER_SEC;
|
|
--sec;
|
|
}
|
|
ts->tv_sec = sec;
|
|
ts->tv_nsec = nsec;
|
|
}
|
|
|
|
notrace static noinline int do_monotonic(struct timespec *ts)
|
|
{
|
|
unsigned long seq, ns, secs;
|
|
do {
|
|
seq = read_seqbegin(>od->lock);
|
|
secs = gtod->wall_time_sec;
|
|
ns = gtod->wall_time_nsec + vgetns();
|
|
secs += gtod->wall_to_monotonic.tv_sec;
|
|
ns += gtod->wall_to_monotonic.tv_nsec;
|
|
} while (unlikely(read_seqretry(>od->lock, seq)));
|
|
vset_normalized_timespec(ts, secs, ns);
|
|
return 0;
|
|
}
|
|
|
|
notrace int __vdso_clock_gettime(clockid_t clock, struct timespec *ts)
|
|
{
|
|
if (likely(gtod->sysctl_enabled && gtod->clock.vread))
|
|
switch (clock) {
|
|
case CLOCK_REALTIME:
|
|
return do_realtime(ts);
|
|
case CLOCK_MONOTONIC:
|
|
return do_monotonic(ts);
|
|
}
|
|
return vdso_fallback_gettime(clock, ts);
|
|
}
|
|
int clock_gettime(clockid_t, struct timespec *)
|
|
__attribute__((weak, alias("__vdso_clock_gettime")));
|
|
|
|
notrace int __vdso_gettimeofday(struct timeval *tv, struct timezone *tz)
|
|
{
|
|
long ret;
|
|
if (likely(gtod->sysctl_enabled && gtod->clock.vread)) {
|
|
if (likely(tv != NULL)) {
|
|
BUILD_BUG_ON(offsetof(struct timeval, tv_usec) !=
|
|
offsetof(struct timespec, tv_nsec) ||
|
|
sizeof(*tv) != sizeof(struct timespec));
|
|
do_realtime((struct timespec *)tv);
|
|
tv->tv_usec /= 1000;
|
|
}
|
|
if (unlikely(tz != NULL)) {
|
|
/* Avoid memcpy. Some old compilers fail to inline it */
|
|
tz->tz_minuteswest = gtod->sys_tz.tz_minuteswest;
|
|
tz->tz_dsttime = gtod->sys_tz.tz_dsttime;
|
|
}
|
|
return 0;
|
|
}
|
|
asm("syscall" : "=a" (ret) :
|
|
"0" (__NR_gettimeofday), "D" (tv), "S" (tz) : "memory");
|
|
return ret;
|
|
}
|
|
int gettimeofday(struct timeval *, struct timezone *)
|
|
__attribute__((weak, alias("__vdso_gettimeofday")));
|