[PATCH 06/16] nanosleep/clock_nanosleep: teach to do compat copyouts

From: Al Viro
Date: Wed Jun 07 2017 - 04:48:20 EST


From: Al Viro <viro@xxxxxxxxxxxxxxxxxx>

Turn restart_block.nanosleep.{rmtp,compat_rmtp} into a tagged union
(kind = 1 -> native, kind = 2 -> compat, kind = 0 -> nothing) and
make the places doing actual copyout handle compat as well as
native (that will become a helper in the next commit). Result:
compat wrappers, messing with reassignments, etc. are gone.

Signed-off-by: Al Viro <viro@xxxxxxxxxxxxxxxxxx>
---
include/linux/posix-timers.h | 2 -
include/linux/restart_block.h | 7 ++-
kernel/compat.c | 131 -----------------------------------------
kernel/time/alarmtimer.c | 16 +++--
kernel/time/hrtimer.c | 38 ++++++++++--
kernel/time/posix-cpu-timers.c | 18 ++++--
kernel/time/posix-stubs.c | 29 ++++++++-
kernel/time/posix-timers.c | 32 +++++++---
8 files changed, 115 insertions(+), 158 deletions(-)

diff --git a/include/linux/posix-timers.h b/include/linux/posix-timers.h
index 667095dbcd37..29f1b7f09ced 100644
--- a/include/linux/posix-timers.h
+++ b/include/linux/posix-timers.h
@@ -110,8 +110,6 @@ void posix_cpu_timers_exit_group(struct task_struct *task);
void set_process_cpu_timer(struct task_struct *task, unsigned int clock_idx,
u64 *newval, u64 *oldval);

-long clock_nanosleep_restart(struct restart_block *restart_block);
-
void update_rlimit_cpu(struct task_struct *task, unsigned long rlim_new);

void posixtimer_rearm(struct siginfo *info);
diff --git a/include/linux/restart_block.h b/include/linux/restart_block.h
index 0d905d8ec553..4509944bd953 100644
--- a/include/linux/restart_block.h
+++ b/include/linux/restart_block.h
@@ -29,10 +29,13 @@ struct restart_block {
/* For nanosleep */
struct {
clockid_t clockid;
- struct timespec __user *rmtp;
+ int kind;
+ union {
+ struct timespec __user *rmtp;
#ifdef CONFIG_COMPAT
- struct compat_timespec __user *compat_rmtp;
+ struct compat_timespec __user *compat_rmtp;
#endif
+ };
u64 expires;
} nanosleep;
/* For poll */
diff --git a/kernel/compat.c b/kernel/compat.c
index cc9ba9d29b47..23afa26f574b 100644
--- a/kernel/compat.c
+++ b/kernel/compat.c
@@ -213,82 +213,6 @@ int compat_convert_timespec(struct timespec __user **kts,
return 0;
}

-static long compat_nanosleep_restart(struct restart_block *restart)
-{
- struct compat_timespec __user *rmtp;
- struct timespec rmt;
- mm_segment_t oldfs;
- long ret;
-
- restart->nanosleep.rmtp = (struct timespec __user *) &rmt;
- oldfs = get_fs();
- set_fs(KERNEL_DS);
- ret = hrtimer_nanosleep_restart(restart);
- set_fs(oldfs);
-
- if (ret == -ERESTART_RESTARTBLOCK) {
- rmtp = restart->nanosleep.compat_rmtp;
-
- if (rmtp && compat_put_timespec(&rmt, rmtp))
- return -EFAULT;
- }
-
- return ret;
-}
-
-COMPAT_SYSCALL_DEFINE2(nanosleep, struct compat_timespec __user *, rqtp,
- struct compat_timespec __user *, rmtp)
-{
- struct timespec tu, rmt;
- struct timespec64 tu64;
- mm_segment_t oldfs;
- long ret;
-
- if (compat_get_timespec(&tu, rqtp))
- return -EFAULT;
-
- tu64 = timespec_to_timespec64(tu);
- if (!timespec64_valid(&tu64))
- return -EINVAL;
-
- oldfs = get_fs();
- set_fs(KERNEL_DS);
- current->restart_block.nanosleep.rmtp =
- rmtp ? (struct timespec __user *)&rmt : NULL;
- ret = hrtimer_nanosleep(&tu64, HRTIMER_MODE_REL, CLOCK_MONOTONIC);
- set_fs(oldfs);
-
- /*
- * hrtimer_nanosleep() can only return 0 or
- * -ERESTART_RESTARTBLOCK here because:
- *
- * - we call it with HRTIMER_MODE_REL and therefor exclude the
- * -ERESTARTNOHAND return path.
- *
- * - we supply the rmtp argument from the task stack (due to
- * the necessary compat conversion. So the update cannot
- * fail, which excludes the -EFAULT return path as well. If
- * it fails nevertheless we have a bigger problem and wont
- * reach this place anymore.
- *
- * - if the return value is 0, we do not have to update rmtp
- * because there is no remaining time.
- *
- * We check for -ERESTART_RESTARTBLOCK nevertheless if the
- * core implementation decides to return random nonsense.
- */
- if (ret == -ERESTART_RESTARTBLOCK) {
- struct restart_block *restart = &current->restart_block;
-
- restart->fn = compat_nanosleep_restart;
- restart->nanosleep.compat_rmtp = rmtp;
-
- if (rmtp && compat_put_timespec(&rmt, rmtp))
- return -EFAULT;
- }
- return ret;
-}
-
static inline long get_compat_itimerval(struct itimerval *o,
struct compat_itimerval __user *i)
{
@@ -821,61 +745,6 @@ COMPAT_SYSCALL_DEFINE2(clock_getres, clockid_t, which_clock,
return err;
}

-static long compat_clock_nanosleep_restart(struct restart_block *restart)
-{
- long err;
- mm_segment_t oldfs;
- struct timespec tu;
- struct compat_timespec __user *rmtp = restart->nanosleep.compat_rmtp;
-
- restart->nanosleep.rmtp = (struct timespec __user *) &tu;
- oldfs = get_fs();
- set_fs(KERNEL_DS);
- err = clock_nanosleep_restart(restart);
- set_fs(oldfs);
-
- if ((err == -ERESTART_RESTARTBLOCK) && rmtp &&
- compat_put_timespec(&tu, rmtp))
- return -EFAULT;
-
- if (err == -ERESTART_RESTARTBLOCK) {
- restart->fn = compat_clock_nanosleep_restart;
- restart->nanosleep.compat_rmtp = rmtp;
- }
- return err;
-}
-
-COMPAT_SYSCALL_DEFINE4(clock_nanosleep, clockid_t, which_clock, int, flags,
- struct compat_timespec __user *, rqtp,
- struct compat_timespec __user *, rmtp)
-{
- long err;
- mm_segment_t oldfs;
- struct timespec in, out;
- struct restart_block *restart;
-
- if (compat_get_timespec(&in, rqtp))
- return -EFAULT;
-
- oldfs = get_fs();
- set_fs(KERNEL_DS);
- err = sys_clock_nanosleep(which_clock, flags,
- (struct timespec __user *) &in,
- (struct timespec __user *) &out);
- set_fs(oldfs);
-
- if ((err == -ERESTART_RESTARTBLOCK) && rmtp &&
- compat_put_timespec(&out, rmtp))
- return -EFAULT;
-
- if (err == -ERESTART_RESTARTBLOCK) {
- restart = &current->restart_block;
- restart->fn = compat_clock_nanosleep_restart;
- restart->nanosleep.compat_rmtp = rmtp;
- }
- return err;
-}
-
/*
* We currently only need the following fields from the sigevent
* structure: sigev_value, sigev_signo, sig_notify and (sometimes
diff --git a/kernel/time/alarmtimer.c b/kernel/time/alarmtimer.c
index d859a3601ddd..868083ae7434 100644
--- a/kernel/time/alarmtimer.c
+++ b/kernel/time/alarmtimer.c
@@ -27,6 +27,7 @@
#include <linux/posix-timers.h>
#include <linux/workqueue.h>
#include <linux/freezer.h>
+#include <linux/compat.h>

#include "posix-timers.h"

@@ -691,7 +692,7 @@ static enum alarmtimer_restart alarmtimer_nsleep_wakeup(struct alarm *alarm,
static int alarmtimer_do_nsleep(struct alarm *alarm, ktime_t absexp,
enum alarmtimer_type type)
{
- struct timespec __user *rmtp;
+ struct restart_block *restart;
alarm->data = (void *)current;
do {
set_current_state(TASK_INTERRUPTIBLE);
@@ -709,8 +710,8 @@ static int alarmtimer_do_nsleep(struct alarm *alarm, ktime_t absexp,

if (freezing(current))
alarmtimer_freezerset(absexp, type);
- rmtp = current->restart_block.nanosleep.rmtp;
- if (rmtp) {
+ restart = &current->restart_block;
+ if (restart->nanosleep.kind) {
struct timespec rmt;
ktime_t rem;

@@ -720,7 +721,14 @@ static int alarmtimer_do_nsleep(struct alarm *alarm, ktime_t absexp,
return 0;
rmt = ktime_to_timespec(rem);

- if (copy_to_user(rmtp, &rmt, sizeof(*rmtp)))
+#ifdef CONFIG_COMPAT
+ if (restart->nanosleep.kind == 2) {
+ if (compat_put_timespec(&rmt,
+ restart->nanosleep.compat_rmtp))
+ return -EFAULT;
+ } else
+#endif
+ if (copy_to_user(restart->nanosleep.rmtp, &rmt, sizeof(rmt)))
return -EFAULT;
}
return -ERESTART_RESTARTBLOCK;
diff --git a/kernel/time/hrtimer.c b/kernel/time/hrtimer.c
index baa7b846b6e3..5534606e3985 100644
--- a/kernel/time/hrtimer.c
+++ b/kernel/time/hrtimer.c
@@ -51,6 +51,7 @@
#include <linux/sched/debug.h>
#include <linux/timer.h>
#include <linux/freezer.h>
+#include <linux/compat.h>

#include <linux/uaccess.h>

@@ -1441,7 +1442,7 @@ EXPORT_SYMBOL_GPL(hrtimer_init_sleeper);

static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mode)
{
- struct timespec __user *rmtp;
+ struct restart_block *restart;
hrtimer_init_sleeper(t, current);

do {
@@ -1461,15 +1462,22 @@ static int __sched do_nanosleep(struct hrtimer_sleeper *t, enum hrtimer_mode mod
if (!t->task)
return 0;

- rmtp = current->restart_block.nanosleep.rmtp;
- if (rmtp) {
+ restart = &current->restart_block;
+ if (restart->nanosleep.kind) {
struct timespec rmt;
ktime_t rem = hrtimer_expires_remaining(&t->timer);
if (rem <= 0)
return 0;
rmt = ktime_to_timespec(rem);

- if (copy_to_user(rmtp, &rmt, sizeof(*rmtp)))
+#ifdef CONFIG_COMPAT
+ if (restart->nanosleep.kind == 2) {
+ if (compat_put_timespec(&rmt,
+ restart->nanosleep.compat_rmtp))
+ return -EFAULT;
+ } else
+#endif
+ if (copy_to_user(restart->nanosleep.rmtp, &rmt, sizeof(rmt)))
return -EFAULT;
}
return -ERESTART_RESTARTBLOCK;
@@ -1535,10 +1543,32 @@ SYSCALL_DEFINE2(nanosleep, struct timespec __user *, rqtp,
if (!timespec64_valid(&tu64))
return -EINVAL;

+ current->restart_block.nanosleep.kind = rmtp ? 1 : 0;
current->restart_block.nanosleep.rmtp = rmtp;
return hrtimer_nanosleep(&tu64, HRTIMER_MODE_REL, CLOCK_MONOTONIC);
}

+#ifdef CONFIG_COMPAT
+
+COMPAT_SYSCALL_DEFINE2(nanosleep, struct compat_timespec __user *, rqtp,
+ struct compat_timespec __user *, rmtp)
+{
+ struct timespec tu;
+ struct timespec64 tu64;
+
+ if (compat_get_timespec(&tu, rqtp))
+ return -EFAULT;
+
+ tu64 = timespec_to_timespec64(tu);
+ if (!timespec64_valid(&tu64))
+ return -EINVAL;
+
+ current->restart_block.nanosleep.kind = rmtp ? 2 : 0;
+ current->restart_block.nanosleep.compat_rmtp = rmtp;
+ return hrtimer_nanosleep(&tu64, HRTIMER_MODE_REL, CLOCK_MONOTONIC);
+}
+#endif
+
/*
* Functions related to boot-time initialization:
*/
diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c
index f999b3ebba7e..b88bc3fc56c5 100644
--- a/kernel/time/posix-cpu-timers.c
+++ b/kernel/time/posix-cpu-timers.c
@@ -12,6 +12,7 @@
#include <trace/events/timer.h>
#include <linux/tick.h>
#include <linux/workqueue.h>
+#include <linux/compat.h>

#include "posix-timers.h"

@@ -1243,8 +1244,7 @@ static int do_cpu_nanosleep(const clockid_t which_clock, int flags,
timer.it_process = current;
if (!error) {
static struct itimerspec64 zero_it;
- struct restart_block *restart = &current->restart_block;
- struct timespec __user *rmtp;
+ struct restart_block *restart;

memset(&it, 0, sizeof it);
it.it_value = *rqtp;
@@ -1311,11 +1311,19 @@ static int do_cpu_nanosleep(const clockid_t which_clock, int flags,
/*
* Report back to the user the time still remaining.
*/
- rmtp = restart->nanosleep.rmtp;
- if (rmtp) {
+ restart = &current->restart_block;
+ if (restart->nanosleep.kind) {
struct timespec ts;
ts = timespec64_to_timespec(it.it_value);
- if (copy_to_user(rmtp, &ts, sizeof(*rmtp)))
+#ifdef CONFIG_COMPAT
+ if (restart->nanosleep.kind == 2) {
+ if (compat_put_timespec(&ts,
+ restart->nanosleep.compat_rmtp))
+ return -EFAULT;
+ } else
+#endif
+ if (copy_to_user(restart->nanosleep.rmtp, &ts,
+ sizeof(ts)))
return -EFAULT;
}
restart->nanosleep.expires = timespec64_to_ns(rqtp);
diff --git a/kernel/time/posix-stubs.c b/kernel/time/posix-stubs.c
index 156a5e6f3bd2..2fe2322d1243 100644
--- a/kernel/time/posix-stubs.c
+++ b/kernel/time/posix-stubs.c
@@ -17,6 +17,7 @@
#include <linux/ktime.h>
#include <linux/timekeeping.h>
#include <linux/posix-timers.h>
+#include <linux/compat.h>

asmlinkage long sys_ni_posix_timers(void)
{
@@ -117,6 +118,7 @@ SYSCALL_DEFINE4(clock_nanosleep, const clockid_t, which_clock, int, flags,
return -EINVAL;
if (flags & TIMER_ABSTIME)
rmtp = NULL;
+ current->restart_block.nanosleep.kind = rmtp ? 1 : 0;
current->restart_block.nanosleep.rmtp = rmtp;
return hrtimer_nanosleep(&t64, flags & TIMER_ABSTIME ?
HRTIMER_MODE_ABS : HRTIMER_MODE_REL,
@@ -127,8 +129,31 @@ SYSCALL_DEFINE4(clock_nanosleep, const clockid_t, which_clock, int, flags,
}

#ifdef CONFIG_COMPAT
-long clock_nanosleep_restart(struct restart_block *restart_block)
+COMPAT_SYSCALL_DEFINE4(clock_nanosleep, clockid_t, which_clock, int, flags,
+ struct compat_timespec __user *, rqtp,
+ struct compat_timespec __user *, rmtp)
{
- return hrtimer_nanosleep_restart(restart_block);
+ struct timespec64 t64;
+ struct timespec t;
+
+ switch (which_clock) {
+ case CLOCK_REALTIME:
+ case CLOCK_MONOTONIC:
+ case CLOCK_BOOTTIME:
+ if (compat_get_timespec(&t, rqtp))
+ return -EFAULT;
+ t64 = timespec_to_timespec64(t);
+ if (!timespec64_valid(&t64))
+ return -EINVAL;
+ if (flags & TIMER_ABSTIME)
+ rmtp = NULL;
+ current->restart_block.nanosleep.kind = rmtp ? 2 : 0;
+ current->restart_block.nanosleep.compat_rmtp = rmtp;
+ return hrtimer_nanosleep(&t64, flags & TIMER_ABSTIME ?
+ HRTIMER_MODE_ABS : HRTIMER_MODE_REL,
+ which_clock);
+ default:
+ return -EINVAL;
+ }
}
#endif
diff --git a/kernel/time/posix-timers.c b/kernel/time/posix-timers.c
index f39ed4bdcc5a..b729e1c0b57d 100644
--- a/kernel/time/posix-timers.c
+++ b/kernel/time/posix-timers.c
@@ -49,6 +49,7 @@
#include <linux/workqueue.h>
#include <linux/export.h>
#include <linux/hashtable.h>
+#include <linux/compat.h>

#include "timekeeping.h"
#include "posix-timers.h"
@@ -1054,25 +1055,40 @@ SYSCALL_DEFINE4(clock_nanosleep, const clockid_t, which_clock, int, flags,
return -EINVAL;
if (flags & TIMER_ABSTIME)
rmtp = NULL;
+ current->restart_block.nanosleep.kind = rmtp ? 1 : 0;
current->restart_block.nanosleep.rmtp = rmtp;

return kc->nsleep(which_clock, flags, &t64);
}

-/*
- * This will restart clock_nanosleep. This is required only by
- * compat_clock_nanosleep_restart for now.
- */
-long clock_nanosleep_restart(struct restart_block *restart_block)
+#ifdef CONFIG_COMPAT
+COMPAT_SYSCALL_DEFINE4(clock_nanosleep, clockid_t, which_clock, int, flags,
+ struct compat_timespec __user *, rqtp,
+ struct compat_timespec __user *, rmtp)
{
- clockid_t which_clock = restart_block->nanosleep.clockid;
const struct k_clock *kc = clockid_to_kclock(which_clock);
+ struct timespec64 t64;
+ struct timespec t;

- if (WARN_ON_ONCE(!kc || !kc->nsleep_restart))
+ if (!kc)
return -EINVAL;
+ if (!kc->nsleep)
+ return -ENANOSLEEP_NOTSUP;
+
+ if (compat_get_timespec(&t, rqtp))
+ return -EFAULT;

- return kc->nsleep_restart(restart_block);
+ t64 = timespec_to_timespec64(t);
+ if (!timespec64_valid(&t64))
+ return -EINVAL;
+ if (flags & TIMER_ABSTIME)
+ rmtp = NULL;
+ current->restart_block.nanosleep.kind = rmtp ? 2 : 0;
+ current->restart_block.nanosleep.compat_rmtp = rmtp;
+
+ return kc->nsleep(which_clock, flags, &t64);
}
+#endif

static const struct k_clock clock_realtime = {
.clock_getres = posix_get_hrtimer_res,
--
2.11.0