Commit bf103a6b authored by Philippe Gerum's avatar Philippe Gerum
Browse files

evl: y2038: sanitize timespec handling at kernel boundary



All timespec passed from/to user-space are now y2038-compliant
(i.e. tv_sec is 64bit wide), using the __evl_timespec and
__evl_itimerspec type definitions at kernel boundary. Conversions
happen back and forth between these types and the timespec64 and
itimerspec64 types used internally.

Invariant: __evl_timespec and __evl_itimerspec are compatible bitwise
with __kernel_timespec and __kernel_itimerspec respectively. libevl
does assume so.

Also:

- The sanitization fixes the ABI so that timespec and itimerspec
  structs are always passed by address, ensuring -EFAULT on invalid
  pointer received from the user, instead of putting the latter at
  risk of SIGSEGV by forcing it to copy/dereference these arguments.

- what EVL_CLKIOC_ADJ_TIME should do was never specified in the
  context of an EVL clock, and no defined use case ever
  existed. However, this service caused a y2038 problem due to the
  legacy timex struct argument. This service was removed from the ABI.
Signed-off-by: Philippe Gerum's avatarPhilippe Gerum <rpm@xenomai.org>
parent 54160f25
......@@ -40,7 +40,7 @@ struct evl_clock {
ktime_t (*read)(struct evl_clock *clock);
u64 (*read_cycles)(struct evl_clock *clock);
int (*set_time)(struct evl_clock *clock,
const struct timespec *ts);
const struct timespec64 *ts);
void (*program_local_shot)(struct evl_clock *clock);
void (*program_remote_shot)(struct evl_clock *clock,
struct evl_rq *rq);
......@@ -48,8 +48,6 @@ struct evl_clock {
const struct evl_clock_gravity *p);
void (*reset_gravity)(struct evl_clock *clock);
void (*adjust)(struct evl_clock *clock);
int (*adjust_time)(struct evl_clock *clock,
struct __kernel_timex *tx);
} ops;
struct evl_timerbase *timerdata;
struct evl_clock *master;
......@@ -109,7 +107,7 @@ static inline ktime_t evl_read_clock(struct evl_clock *clock)
static inline int
evl_set_clock_time(struct evl_clock *clock,
const struct timespec *ts)
const struct timespec64 *ts)
{
if (clock->ops.set_time)
return clock->ops.set_time(clock, ts);
......@@ -148,16 +146,6 @@ static inline void evl_reset_clock_gravity(struct evl_clock *clock)
#define evl_get_clock_gravity(__clock, __type) ((__clock)->gravity.__type)
static inline
int evl_clock_adjust_time(struct evl_clock *clock,
struct __kernel_timex *tx)
{
if (clock->ops.adjust_time)
return clock->ops.adjust_time(clock, tx);
return -EOPNOTSUPP;
}
int evl_clock_init(void);
void evl_clock_cleanup(void);
......@@ -174,4 +162,62 @@ static inline void evl_put_clock(struct evl_clock *clock)
evl_put_element(&clock->element);
}
static inline ktime_t
u_timespec_to_ktime(const struct __evl_timespec u_ts)
{
struct timespec64 ts64 = (struct timespec64){
.tv_sec = u_ts.tv_sec,
.tv_nsec = u_ts.tv_nsec,
};
return timespec64_to_ktime(ts64);
}
static inline struct __evl_timespec
ktime_to_u_timespec(ktime_t t)
{
struct timespec64 ts64 = ktime_to_timespec64(t);
return (struct __evl_timespec){
.tv_sec = ts64.tv_sec,
.tv_nsec = ts64.tv_nsec,
};
}
static inline struct timespec64
u_timespec_to_timespec64(const struct __evl_timespec u_ts)
{
return (struct timespec64){
.tv_sec = u_ts.tv_sec,
.tv_nsec = u_ts.tv_nsec,
};
}
static inline struct __evl_timespec
timespec64_to_u_timespec(const struct timespec64 ts64)
{
return (struct __evl_timespec){
.tv_sec = ts64.tv_sec,
.tv_nsec = ts64.tv_nsec,
};
}
static inline struct itimerspec64
u_itimerspec_to_itimerspec64(const struct __evl_itimerspec u_its)
{
return (struct itimerspec64){
.it_value = u_timespec_to_timespec64(u_its.it_value),
.it_interval = u_timespec_to_timespec64(u_its.it_interval),
};
}
static inline struct __evl_itimerspec
itimerspec64_to_u_itimerspec(const struct itimerspec64 its64)
{
return (struct __evl_itimerspec){
.it_value = timespec64_to_u_timespec(its64.it_value),
.it_interval = timespec64_to_u_timespec(its64.it_interval),
};
}
#endif /* !_EVL_CLOCK_H */
......@@ -186,7 +186,7 @@ DECLARE_EVENT_CLASS(evl_sched_attrs,
);
DECLARE_EVENT_CLASS(evl_clock_timespec,
TP_PROTO(struct evl_clock *clock, const struct timespec *val),
TP_PROTO(struct evl_clock *clock, const struct timespec64 *val),
TP_ARGS(clock, val),
TP_STRUCT__entry(
......@@ -199,7 +199,7 @@ DECLARE_EVENT_CLASS(evl_clock_timespec,
__assign_str(name, clock->name);
),
TP_printk("clock=%s timeval=(%ld.%09ld)",
TP_printk("clock=%s timeval=(%lld.%09lld)",
__get_str(name),
__timespec_args(val)
)
......@@ -727,8 +727,8 @@ DEFINE_EVENT(mutex_event, evl_mutex_flush,
);
#define __timespec_fields(__name) \
__field(__kernel_time_t, tv_sec_##__name) \
__field(long, tv_nsec_##__name)
__field(__kernel_time64_t, tv_sec_##__name) \
__field(long long, tv_nsec_##__name)
#define __assign_timespec(__to, __from) \
do { \
......@@ -793,17 +793,17 @@ TRACE_EVENT(evl_thread_update_mode,
);
DEFINE_EVENT(evl_clock_timespec, evl_clock_getres,
TP_PROTO(struct evl_clock *clock, const struct timespec *res),
TP_PROTO(struct evl_clock *clock, const struct timespec64 *res),
TP_ARGS(clock, res)
);
DEFINE_EVENT(evl_clock_timespec, evl_clock_gettime,
TP_PROTO(struct evl_clock *clock, const struct timespec *time),
TP_PROTO(struct evl_clock *clock, const struct timespec64 *time),
TP_ARGS(clock, time)
);
DEFINE_EVENT(evl_clock_timespec, evl_clock_settime,
TP_PROTO(struct evl_clock *clock, const struct timespec *time),
TP_PROTO(struct evl_clock *clock, const struct timespec64 *time),
TP_ARGS(clock, time)
);
......
......@@ -7,6 +7,8 @@
#ifndef _EVL_UAPI_CLOCK_H
#define _EVL_UAPI_CLOCK_H
#include <uapi/evl/types.h>
#define EVL_CLOCK_MONOTONIC_DEV "monotonic"
#define EVL_CLOCK_REALTIME_DEV "realtime"
#define EVL_CLOCK_DEV "clock"
......@@ -16,38 +18,20 @@
#define EVL_CLOCK_IOCBASE 'c'
#ifndef __KERNEL__ /* Eeek. */
#define __user_timex timex
#else
#define __user_timex __kernel_timex
#endif
struct evl_clock_sleepreq {
struct timespec timeout;
};
#define EVL_CLKIOC_SLEEP _IOWR(EVL_CLOCK_IOCBASE, 0, struct evl_clock_sleepreq)
#define EVL_CLKIOC_GET_RES _IOR(EVL_CLOCK_IOCBASE, 1, struct timespec)
#define EVL_CLKIOC_GET_TIME _IOR(EVL_CLOCK_IOCBASE, 2, struct timespec)
#define EVL_CLKIOC_SET_TIME _IOR(EVL_CLOCK_IOCBASE, 3, struct timespec)
#define EVL_CLKIOC_ADJ_TIME _IOR(EVL_CLOCK_IOCBASE, 4, struct __user_timex)
#define EVL_CLKIOC_SLEEP _IOW(EVL_CLOCK_IOCBASE, 0, struct __evl_timespec)
#define EVL_CLKIOC_GET_RES _IOR(EVL_CLOCK_IOCBASE, 1, struct __evl_timespec)
#define EVL_CLKIOC_GET_TIME _IOR(EVL_CLOCK_IOCBASE, 2, struct __evl_timespec)
#define EVL_CLKIOC_SET_TIME _IOW(EVL_CLOCK_IOCBASE, 3, struct __evl_timespec)
#define EVL_CLKIOC_NEW_TIMER _IO(EVL_CLOCK_IOCBASE, 5)
/* Set operation flag for timers. */
#define EVL_TIMERFD_ABSTIME 0x1
struct evl_timerfd_setreq {
struct itimerspec *value;
struct itimerspec *ovalue;
};
struct evl_timerfd_getreq {
struct itimerspec *value;
struct __evl_itimerspec *value;
struct __evl_itimerspec *ovalue;
};
#define EVL_TIMERFD_IOCBASE 't'
#define EVL_TFDIOC_SET _IOWR(EVL_TIMERFD_IOCBASE, 0, struct evl_timerfd_setreq)
#define EVL_TFDIOC_GET _IOR(EVL_TIMERFD_IOCBASE, 1, struct evl_timerfd_getreq)
#define EVL_TFDIOC_SET _IOWR(EVL_TIMERFD_IOCBASE, 0, struct evl_timerfd_setreq)
#define EVL_TFDIOC_GET _IOR(EVL_TIMERFD_IOCBASE, 1, struct __evl_itimerspec)
#endif /* !_EVL_UAPI_CLOCK_H */
......@@ -11,14 +11,14 @@
#include <uapi/evl/sched.h>
/* Earliest ABI level we support. */
#define EVL_ABI_BASE 18
#define EVL_ABI_BASE 19
/*
* Current/latest ABI level we support. We may decouple the base and
* current ABI levels by providing backward compatibility from the
* latter to the former. CAUTION: a litteral value is required for the
* current ABI definition (scripts reading this may be naive).
*/
#define EVL_ABI_LEVEL 18
#define EVL_ABI_LEVEL 19
#define EVL_CONTROL_DEV "/dev/evl/control"
......
......@@ -52,12 +52,8 @@ struct evl_monitor_state {
} u;
};
struct evl_monitor_lockreq {
struct timespec timeout;
};
struct evl_monitor_waitreq {
struct timespec timeout;
struct __evl_timespec *timeout;
__s32 gatefd;
__s32 status;
__s32 value;
......@@ -75,7 +71,7 @@ struct evl_monitor_binding {
#define EVL_MONITOR_IOCBASE 'm'
#define EVL_MONIOC_ENTER _IOW(EVL_MONITOR_IOCBASE, 0, struct evl_monitor_lockreq)
#define EVL_MONIOC_ENTER _IOW(EVL_MONITOR_IOCBASE, 0, struct __evl_timespec)
#define EVL_MONIOC_TRYENTER _IO(EVL_MONITOR_IOCBASE, 1)
#define EVL_MONIOC_EXIT _IO(EVL_MONITOR_IOCBASE, 2)
#define EVL_MONIOC_WAIT _IOWR(EVL_MONITOR_IOCBASE, 3, struct evl_monitor_waitreq)
......
......@@ -7,7 +7,7 @@
#ifndef _EVL_UAPI_POLL_H
#define _EVL_UAPI_POLL_H
#include <linux/types.h>
#include <uapi/evl/types.h>
#define EVL_POLL_DEV "poll"
......@@ -29,7 +29,7 @@ struct evl_poll_event {
};
struct evl_poll_waitreq {
struct timespec timeout;
struct __evl_timespec *timeout;
struct evl_poll_event *pollset;
int nrset;
};
......
......@@ -8,7 +8,7 @@
#ifndef _EVL_UAPI_SCHED_H
#define _EVL_UAPI_SCHED_H
#include <linux/types.h>
#include <uapi/evl/types.h>
#define EVL_CPU_OOB (1 << 0)
#define EVL_CPU_ISOL (1 << 1)
......@@ -19,7 +19,7 @@
#define sched_rr_quantum sched_u.rr.__sched_rr_quantum
struct __sched_rr_param {
struct timespec __sched_rr_quantum;
struct __evl_timespec __sched_rr_quantum;
};
#define SCHED_QUOTA 44
......@@ -78,8 +78,8 @@ struct evl_tp_ctlparam {
} op;
int nr_windows;
struct __sched_tp_window {
struct timespec offset;
struct timespec duration;
struct __evl_timespec offset;
struct __evl_timespec duration;
int ptid;
} windows[0];
};
......
......@@ -28,4 +28,20 @@ static inline fundle_t evl_get_index(fundle_t handle)
return handle & ~EVL_HANDLE_INDEX_MASK;
}
/*
* Y2038 safety. Match the kernel ABI definitions of __kernel_timespec
* and __kernel_itimerspec.
*/
typedef long long __evl_time64_t;
struct __evl_timespec {
__evl_time64_t tv_sec;
long long tv_nsec;
};
struct __evl_itimerspec {
struct __evl_timespec it_interval;
struct __evl_timespec it_value;
};
#endif /* !_EVL_UAPI_TYPES_H */
......@@ -16,6 +16,4 @@ struct evl_xbuf_attrs {
__u32 o_bufsz;
};
#define EVL_XBUF_IOCBASE 'x'
#endif /* !_EVL_UAPI_XBUF_H */
......@@ -474,27 +474,11 @@ static long restart_clock_sleep(struct restart_block *param)
}
static int clock_sleep(struct evl_clock *clock,
struct evl_clock_sleepreq __user *u_req)
struct timespec64 ts64)
{
struct evl_clock_sleepreq req = {
.timeout = {
.tv_sec = 0, .tv_nsec = 0
},
};
struct evl_thread *curr = evl_current();
struct restart_block *restart;
ktime_t timeout, rem;
int ret;
ret = raw_copy_from_user(&req, u_req, sizeof(req));
if (ret)
return -EFAULT;
if (req.timeout.tv_sec < 0)
return -EINVAL;
if ((unsigned long)req.timeout.tv_nsec >= ONE_BILLION)
return -EINVAL;
if (curr->local_info & T_SYSRST) {
curr->local_info &= ~T_SYSRST;
......@@ -503,7 +487,7 @@ static int clock_sleep(struct evl_clock *clock,
return -EINTR;
timeout = restart->nanosleep.expires;
} else
timeout = timespec_to_ktime(req.timeout);
timeout = timespec64_to_ktime(ts64);
rem = evl_delay_thread(timeout, EVL_ABS, clock);
if (!rem)
......@@ -520,76 +504,45 @@ static int clock_sleep(struct evl_clock *clock,
return -EINTR;
}
static int get_clock_resolution(struct evl_clock *clock,
struct timespec __user *u_res)
static void get_clock_resolution(struct evl_clock *clock,
struct timespec64 *res)
{
struct timespec res;
*res = ktime_to_timespec64(evl_get_clock_resolution(clock));
res = ktime_to_timespec(evl_get_clock_resolution(clock));
trace_evl_clock_getres(clock, &res);
return raw_copy_to_user(u_res, &res, sizeof(res)) ? -EFAULT : 0;
trace_evl_clock_getres(clock, res);
}
static int get_clock_time(struct evl_clock *clock,
struct timespec __user *u_ts)
static void get_clock_time(struct evl_clock *clock,
struct timespec64 *ts)
{
struct timespec ts;
ts = ktime_to_timespec(evl_read_clock(clock));
*ts = ktime_to_timespec64(evl_read_clock(clock));
trace_evl_clock_gettime(clock, &ts);
return raw_copy_to_user(u_ts, &ts, sizeof(ts)) ? -EFAULT : 0;
trace_evl_clock_gettime(clock, ts);
}
static int set_clock_time(struct evl_clock *clock,
struct timespec __user *u_ts)
struct timespec64 ts)
{
struct timespec ts;
int ret;
ret = raw_copy_from_user(&ts, u_ts, sizeof(ts));
if (ret)
return -EFAULT;
if ((unsigned long)ts.tv_nsec >= ONE_BILLION)
return -EINVAL;
trace_evl_clock_settime(clock, &ts);
return evl_set_clock_time(clock, &ts);
}
static int adjust_clock_time(struct evl_clock *clock,
struct __kernel_timex __user *u_tx)
{
struct __kernel_timex tx;
int ret;
ret = raw_copy_from_user(&tx, u_tx, sizeof(tx));
if (ret)
return -EFAULT;
return evl_clock_adjust_time(clock, &tx);
}
static void get_timer_value(struct evl_timer *__restrict__ timer,
struct itimerspec *__restrict__ value)
struct itimerspec64 *__restrict__ value)
{
value->it_interval = ktime_to_timespec(timer->interval);
value->it_interval = ktime_to_timespec64(timer->interval);
if (!evl_timer_is_running(timer)) {
value->it_value.tv_sec = 0;
value->it_value.tv_nsec = 0;
} else
value->it_value =
ktime_to_timespec(evl_get_timer_delta(timer));
ktime_to_timespec64(evl_get_timer_delta(timer));
}
static int set_timer_value(struct evl_timer *__restrict__ timer,
const struct itimerspec *__restrict__ value)
const struct itimerspec64 *__restrict__ value)
{
ktime_t start, period;
......@@ -598,14 +551,8 @@ static int set_timer_value(struct evl_timer *__restrict__ timer,
return 0;
}
if ((unsigned long)value->it_value.tv_nsec >= ONE_BILLION ||
((unsigned long)value->it_interval.tv_nsec >= ONE_BILLION &&
(value->it_value.tv_sec != 0 ||
value->it_value.tv_nsec != 0)))
return -EINVAL;
period = timespec_to_ktime(value->it_interval);
start = timespec_to_ktime(value->it_value);
period = timespec64_to_ktime(value->it_interval);
start = timespec64_to_ktime(value->it_value);
evl_start_timer(timer, start, period);
return 0;
......@@ -641,8 +588,8 @@ static inline void pin_timer(struct evl_timer *timer)
#endif
static int set_timerfd(struct evl_timerfd *timerfd,
const struct itimerspec *__restrict__ value,
struct itimerspec *__restrict__ ovalue)
const struct itimerspec64 *__restrict__ value,
struct itimerspec64 *__restrict__ ovalue)
{
get_timer_value(&timerfd->timer, ovalue);
pin_timer(&timerfd->timer);
......@@ -674,9 +621,9 @@ static long timerfd_common_ioctl(struct file *filp,
unsigned int cmd, unsigned long arg)
{
struct evl_timerfd *timerfd = filp->private_data;
struct __evl_itimerspec uits, uoits, __user *u_uits;
struct evl_timerfd_setreq sreq, __user *u_sreq;
struct evl_timerfd_getreq greq, __user *u_greq;
struct itimerspec value, ovalue;
struct itimerspec64 its, oits;
long ret = 0;
switch (cmd) {
......@@ -686,23 +633,30 @@ static long timerfd_common_ioctl(struct file *filp,
ret = raw_copy_from_user(&sreq, u_sreq, sizeof(sreq));
if (ret)
return -EFAULT;
ret = raw_copy_from_user(&value, sreq.value, sizeof(value));
ret = raw_copy_from_user(&uits, sreq.value, sizeof(uits));
if (ret)
return -EFAULT;
ret = set_timerfd(timerfd, &value, &ovalue);
if ((unsigned long)uits.it_value.tv_nsec >= ONE_BILLION ||
((unsigned long)uits.it_interval.tv_nsec >= ONE_BILLION &&
(uits.it_value.tv_sec != 0 ||
uits.it_value.tv_nsec != 0)))
return -EINVAL;
its = u_itimerspec_to_itimerspec64(uits);
ret = set_timerfd(timerfd, &its, &oits);
if (ret)
return ret;
if (sreq.ovalue &&
raw_copy_to_user(sreq.ovalue, &ovalue, sizeof(ovalue)))
return -EFAULT;
if (sreq.ovalue) {
uoits = itimerspec64_to_u_itimerspec(oits);
u_uits = (typeof(u_uits))sreq.ovalue;
if (raw_copy_to_user(u_uits, &uoits, sizeof(uoits)))
return -EFAULT;
}
break;
case EVL_TFDIOC_GET:
u_greq = (typeof(u_greq))arg;
ret = raw_copy_from_user(&greq, u_greq, sizeof(greq));
if (ret)
return -EFAULT;
get_timer_value(&timerfd->timer, &value);
if (raw_copy_to_user(greq.value, &value, sizeof(value)))
get_timer_value(&timerfd->timer, &its);
uits = itimerspec64_to_u_itimerspec(its);
u_uits = (typeof(u_uits))arg;
if (raw_copy_to_user(u_uits, &uits, sizeof(uits)))
return -EFAULT;
break;
default:
......@@ -825,24 +779,34 @@ static int new_timerfd(struct evl_clock *clock)
static long clock_common_ioctl(struct evl_clock *clock,
unsigned int cmd, unsigned long arg)
{
struct __evl_timespec uts, __user *u_uts;
struct timespec64 ts64;
int ret;
switch (cmd) {
case EVL_CLKIOC_GET_RES:
ret = get_clock_resolution(clock,
(struct timespec __user *)arg);
get_clock_resolution(clock, &ts64);
uts = timespec64_to_u_timespec(ts64);
u_uts = (typeof(u_uts))arg;
ret = raw_copy_to_user(u_uts, &uts,
sizeof(*u_uts)) ? -EFAULT : 0;
break;
case EVL_CLKIOC_GET_TIME:
ret = get_clock_time(clock,
(struct timespec __user *)arg);
get_clock_time(clock, &ts64);
uts = timespec64_to_u_timespec(ts64);
u_uts = (typeof(u_uts))arg;
ret = raw_copy_to_user(u_uts, &uts,
sizeof(uts)) ? -EFAULT : 0;
break;
case EVL_CLKIOC_SET_TIME:
ret = set_clock_time(clock,
(struct timespec __user *)arg);
break;
case EVL_CLKIOC_ADJ_TIME:
ret = adjust_clock_time(clock,
(struct __kernel_timex __user *)arg);
u_uts = (typeof(u_uts))arg;
ret = raw_copy_from_user(&uts, u_uts, sizeof(uts));
if (ret)
return -EFAULT;
if ((unsigned long)uts.tv_nsec >= ONE_BILLION)
return -EINVAL;
ts64 = u_timespec_to_timespec64(uts);
ret = set_clock_time(clock, ts64);
break;
default:
ret = -ENOTTY;
......@@ -855,12 +819,29 @@ static long clock_oob_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct evl_clock *clock = element_of(filp, struct evl_clock);
struct __evl_timespec __user *u_uts;
struct __evl_timespec uts = {
.tv_sec = 0,
.tv_nsec = 0,
};
int ret;
switch (cmd) {
case EVL_CLKIOC_SLEEP:
ret = clock_sleep(clock,
(struct evl_clock_sleepreq __user *)arg);
u_uts = (typeof(u_uts))arg;
ret = raw_copy_from_user(&uts, u_uts, sizeof(uts));
if (ret)
return -EFAULT;
if (uts.tv_sec < 0)
return -EINVAL;
/*
* CAUTION: the user-provided type is wider than our
* internal type, we need to check ranges prior to
* converting to timespec64.
*/
if ((unsigned long)uts.tv_nsec >= ONE_BILLION)
return -EINVAL;
ret = clock_sleep(clock, u_timespec_to_timespec64(uts));
break;
default:
ret = clock_common_ioctl(clock, cmd, arg);
......
......@@ -189,16 +189,13 @@ static void wakeup_waiters(struct evl_monitor *event)
}
static int __enter_monitor(struct evl_monitor *gate,
struct evl_monitor_lockreq *req)
struct timespec64 *ts64)
{
ktime_t timeout = EVL_INFINITE;
enum evl_tmode tmode;
if (req) {
if ((unsigned long)req->timeout.tv_nsec >= ONE_BILLION)
return -EINVAL;
timeout = timespec_to_ktime(req->timeout);