Skip to contentSkip to conversation

x86: fix get_nr_restart_syscall()

PATCH RESEND Applied Oleg Nesterov 5 participants 20 messages Feb 1, 2021 – Mar 16, 2021

Subsystems (6)
lore
Share & tools
lore
#0 lore raw

v4 cover x86: fix get_nr_restart_syscall()

Somehow I forgot about this problem. Let me resend the last version based on discussion with Linus. IIRC he was agree with this series.

And let me remind why 3/4 temporary adds the "transient" TS_COMPAT_RESTART flag killed by the next patch: to simplify the backporting. 1-3 can fix the problem without breaking the kABI.

Oleg.

---
 arch/x86/include/asm/processor.h   |  9 ---------
 arch/x86/include/asm/thread_info.h | 15 ++++++++++++++-
 arch/x86/kernel/signal.c           | 24 +-----------------------
 fs/select.c                        | 10 ++++------
 include/linux/restart_block.h      |  1 +
 include/linux/thread_info.h        | 13 +++++++++++++
 kernel/futex.c                     |  3 +--
 kernel/time/alarmtimer.c           |  2 +-
 kernel/time/hrtimer.c              |  2 +-
 kernel/time/posix-cpu-timers.c     |  2 +-
 10 files changed, 37 insertions(+), 44 deletions(-)
#1 lore raw

PATCH v4 1/4 introduce set_restart_fn() and arch_set_restart_data()

Preparation. Add the new helper which sets restart_block->fn and calls the dummy arch_set_restart_data() helper.

---
 fs/select.c                    | 10 ++++------
 include/linux/thread_info.h    | 13 +++++++++++++
 kernel/futex.c                 |  3 +--
 kernel/time/alarmtimer.c       |  2 +-
 kernel/time/hrtimer.c          |  2 +-
 kernel/time/posix-cpu-timers.c |  2 +-
 6 files changed, 21 insertions(+), 11 deletions(-)
fs/select.c +4 -6
index 37aaa8317f3a..945896d0ac9e 100644
@@ -1055,10 +1055,9 @@ static long do_restart_poll(struct restart_block *restart_block)
 
 	ret = do_sys_poll(ufds, nfds, to);
 
-	if (ret == -ERESTARTNOHAND) {
-		restart_block->fn = do_restart_poll;
-		ret = -ERESTART_RESTARTBLOCK;
-	}
+	if (ret == -ERESTARTNOHAND)
+		ret = set_restart_fn(restart_block, do_restart_poll);
+
 	return ret;
 }
 
@@ -1080,7 +1079,6 @@ SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
 		struct restart_block *restart_block;
 
 		restart_block = &current->restart_block;
-		restart_block->fn = do_restart_poll;
 		restart_block->poll.ufds = ufds;
 		restart_block->poll.nfds = nfds;
 
@@ -1091,7 +1089,7 @@ SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
 		} else
 			restart_block->poll.has_timeout = 0;
 
-		ret = -ERESTART_RESTARTBLOCK;
+		ret = set_restart_fn(restart_block, do_restart_poll);
 	}
 	return ret;
 }
include/linux/thread_info.h +13 -0
index c8a974cead73..495fb6a94e58 100644
@@ -11,6 +11,7 @@
 #include <linux/types.h>
 #include <linux/bug.h>
 #include <linux/restart_block.h>
+#include <linux/errno.h>
 
 #ifdef CONFIG_THREAD_INFO_IN_TASK
 /*
@@ -57,6 +58,18 @@ enum syscall_work_bit {
 
 #ifdef __KERNEL__
 
+#ifndef arch_set_restart_data
+#define arch_set_restart_data(restart) do { } while (0)
+#endif
+
+static inline long set_restart_fn(struct restart_block *restart,
+					long (*fn)(struct restart_block *))
+{
+	restart->fn = fn;
+	arch_set_restart_data(restart);
+	return -ERESTART_RESTARTBLOCK;
+}
+
 #ifndef THREAD_ALIGN
 #define THREAD_ALIGN	THREAD_SIZE
 #endif
kernel/futex.c +1 -2
index c47d1015d759..1ec50f9681a6 100644
@@ -2724,14 +2724,13 @@ static int futex_wait(u32 __user *uaddr, unsigned int flags, u32 val,
 		goto out;
 
 	restart = &current->restart_block;
-	restart->fn = futex_wait_restart;
 	restart->futex.uaddr = uaddr;
 	restart->futex.val = val;
 	restart->futex.time = *abs_time;
 	restart->futex.bitset = bitset;
 	restart->futex.flags = flags | FLAGS_HAS_TIMEOUT;
 
-	ret = -ERESTART_RESTARTBLOCK;
+	ret = set_restart_fn(restart, futex_wait_restart);
 
 out:
 	if (to) {
kernel/time/alarmtimer.c +1 -1
index f4ace1bf8382..daeaa7140d0a 100644
@@ -848,9 +848,9 @@ static int alarm_timer_nsleep(const clockid_t which_clock, int flags,
 	if (flags == TIMER_ABSTIME)
 		return -ERESTARTNOHAND;
 
-	restart->fn = alarm_timer_nsleep_restart;
 	restart->nanosleep.clockid = type;
 	restart->nanosleep.expires = exp;
+	set_restart_fn(restart, alarm_timer_nsleep_restart);
 	return ret;
 }
 
kernel/time/hrtimer.c +1 -1
index 743c852e10f2..30f936431c56 100644
@@ -1939,9 +1939,9 @@ long hrtimer_nanosleep(ktime_t rqtp, const enum hrtimer_mode mode,
 	}
 
 	restart = &current->restart_block;
-	restart->fn = hrtimer_nanosleep_restart;
 	restart->nanosleep.clockid = t.timer.base->clockid;
 	restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer);
+	set_restart_fn(restart, hrtimer_nanosleep_restart);
 out:
 	destroy_hrtimer_on_stack(&t.timer);
 	return ret;
kernel/time/posix-cpu-timers.c +1 -1
index a71758e34e45..9abe15255bc4 100644
@@ -1480,8 +1480,8 @@ static int posix_cpu_nsleep(const clockid_t which_clock, int flags,
 		if (flags & TIMER_ABSTIME)
 			return -ERESTARTNOHAND;
 
-		restart_block->fn = posix_cpu_nsleep_restart;
 		restart_block->nanosleep.clockid = which_clock;
+		set_restart_fn(restart_block, posix_cpu_nsleep_restart);
 	}
 	return error;
 }
Signature · 1 line
2.25.1.362.g51ebf55
#5 lore raw

PATCH v4 3/4 x86: introduce TS_COMPAT_RESTART to fix

In reply to Oleg Nesterov

The comment in get_nr_restart_syscall() says:

* The problem is that we can get here when ptrace pokes
* syscall-like values into regs even if we're not in a syscall
* at all.

Yes. but if we are not in syscall then the

	status & (TS_COMPAT|TS_I386_REGS_POKED)

check below can't really help:

- TS_COMPAT can't be set

- TS_I386_REGS_POKED is only set if regs->orig_ax was changed by 32bit debugger; and even in this case get_nr_restart_syscall() is only correct if the tracee is 32bit too.

Suppose that 64bit debugger plays with 32bit tracee and

* Tracee calls sleep(2) // TS_COMPAT is set
* User interrupts the tracee by CTRL-C after 1 sec and does "(gdb) call func()"
* gdb saves the regs by PTRACE_GETREGS
* does PTRACE_SETREGS to set %rip='func' and %orig_rax=-1
* PTRACE_CONT // TS_COMPAT is cleared
* func() hits int3.
* Debugger catches SIGTRAP.
* Restore original regs by PTRACE_SETREGS.
* PTRACE_CONT

get_nr_restart_syscall() wrongly returns __NR_restart_syscall==219, the tracee calls ia32_sys_call_table[219] == sys_madvise.

This patch adds the sticky TS_COMPAT_RESTART flag which survives after return to user mode, hopefully it allows us to kill TS_I386_REGS_POKED but this needs another patch.

Test-case:

  $ cvs -d :pserver:anoncvs:[email protected]:/cvs/systemtap co ptrace-tests
  $ gcc -o erestartsys-trap-debuggee ptrace-tests/tests/erestartsys-trap-debuggee.c --m32
  $ gcc -o erestartsys-trap-debugger ptrace-tests/tests/erestartsys-trap-debugger.c -lutil
  $ ./erestartsys-trap-debugger
  Unexpected: retval 1, errno 22
  erestartsys-trap-debugger: ptrace-tests/tests/erestartsys-trap-debugger.c:421
---
 arch/x86/include/asm/thread_info.h | 14 +++++++++++++-
 arch/x86/kernel/signal.c           | 24 +-----------------------
 2 files changed, 14 insertions(+), 24 deletions(-)
arch/x86/include/asm/thread_info.h +13 -1
index c2dc29e215ea..30d1d187019f 100644
@@ -214,10 +214,22 @@ static inline int arch_within_stack_frames(const void * const stack,
  */
 #define TS_COMPAT		0x0002	/* 32bit syscall active (64BIT)*/
 
+#ifndef __ASSEMBLY__
 #ifdef CONFIG_COMPAT
 #define TS_I386_REGS_POKED	0x0004	/* regs poked by 32-bit ptracer */
+#define TS_COMPAT_RESTART	0x0008
+
+#define arch_set_restart_data	arch_set_restart_data
+
+static inline void arch_set_restart_data(struct restart_block *restart)
+{
+	struct thread_info *ti = current_thread_info();
+	if (ti->status & TS_COMPAT)
+		ti->status |= TS_COMPAT_RESTART;
+	else
+		ti->status &= ~TS_COMPAT_RESTART;
+}
 #endif
-#ifndef __ASSEMBLY__
 
 #ifdef CONFIG_X86_32
 #define in_ia32_syscall() true
arch/x86/kernel/signal.c +1 -23
index ea794a083c44..6c26d2c3a2e4 100644
@@ -766,30 +766,8 @@ handle_signal(struct ksignal *ksig, struct pt_regs *regs)
 
 static inline unsigned long get_nr_restart_syscall(const struct pt_regs *regs)
 {
-	/*
-	 * This function is fundamentally broken as currently
-	 * implemented.
-	 *
-	 * The idea is that we want to trigger a call to the
-	 * restart_block() syscall and that we want in_ia32_syscall(),
-	 * in_x32_syscall(), etc. to match whatever they were in the
-	 * syscall being restarted.  We assume that the syscall
-	 * instruction at (regs->ip - 2) matches whatever syscall
-	 * instruction we used to enter in the first place.
-	 *
-	 * The problem is that we can get here when ptrace pokes
-	 * syscall-like values into regs even if we're not in a syscall
-	 * at all.
-	 *
-	 * For now, we maintain historical behavior and guess based on
-	 * stored state.  We could do better by saving the actual
-	 * syscall arch in restart_block or (with caveats on x32) by
-	 * checking if regs->ip points to 'int $0x80'.  The current
-	 * behavior is incorrect if a tracer has a different bitness
-	 * than the tracee.
-	 */
 #ifdef CONFIG_IA32_EMULATION
-	if (current_thread_info()->status & (TS_COMPAT|TS_I386_REGS_POKED))
+	if (current_thread_info()->status & TS_COMPAT_RESTART)
 		return __NR_ia32_restart_syscall;
 #endif
 #ifdef CONFIG_X86_X32_ABI
Signature · 1 line
2.25.1.362.g51ebf55
#3 lore raw

PATCH v4 2/4 x86: mv TS_COMPAT from asm/processor.h to

In reply to Oleg Nesterov

Move TS_COMPAT back to asm/thread_info.h, close to TS_I386_REGS_POKED.

It was moved to asm/processor.h by b9d989c7218a ("x86/asm: Move the thread_info::status field to thread_struct"), then later 37a8f7c38339 ("x86/asm: Move 'status' from thread_struct to thread_info") moved the 'status' field back but TS_COMPAT was forgotten.

---
 arch/x86/include/asm/processor.h   | 9 ---------
 arch/x86/include/asm/thread_info.h | 9 +++++++++
 2 files changed, 9 insertions(+), 9 deletions(-)
arch/x86/include/asm/processor.h +0 -9
index c20a52b5534b..c66df6368909 100644
@@ -552,15 +552,6 @@ static inline void arch_thread_struct_whitelist(unsigned long *offset,
 	*size = fpu_kernel_xstate_size;
 }
 
-/*
- * Thread-synchronous status.
- *
- * This is different from the flags in that nobody else
- * ever touches our thread-synchronous status, so we don't
- * have to worry about atomic accesses.
- */
-#define TS_COMPAT		0x0002	/* 32bit syscall active (64BIT)*/
-
 static inline void
 native_load_sp0(unsigned long sp0)
 {
arch/x86/include/asm/thread_info.h +9 -0
index 0d751d5da702..c2dc29e215ea 100644
@@ -205,6 +205,15 @@ static inline int arch_within_stack_frames(const void * const stack,
 
 #endif
 
+/*
+ * Thread-synchronous status.
+ *
+ * This is different from the flags in that nobody else
+ * ever touches our thread-synchronous status, so we don't
+ * have to worry about atomic accesses.
+ */
+#define TS_COMPAT		0x0002	/* 32bit syscall active (64BIT)*/
+
 #ifdef CONFIG_COMPAT
 #define TS_I386_REGS_POKED	0x0004	/* regs poked by 32-bit ptracer */
 #endif
Signature · 1 line
2.25.1.362.g51ebf55
#10 lore raw

PATCH v4 4/4 x86: introduce restart_block->arch_data to kill

In reply to Oleg Nesterov

With this patch x86 just saves current_thread_info()->status in the new restart_block->arch_data field, TS_COMPAT_RESTART can be removed.

Rather than saving "status" we could shift the code from get_nr_restart_syscall() to arch_set_restart_data() and save the syscall number in ->arch_data.

---
 arch/x86/include/asm/thread_info.h | 12 ++----------
 arch/x86/kernel/signal.c           |  2 +-
 include/linux/restart_block.h      |  1 +
 3 files changed, 4 insertions(+), 11 deletions(-)
arch/x86/include/asm/thread_info.h +2 -10
index 30d1d187019f..06b740bae431 100644
@@ -217,18 +217,10 @@ static inline int arch_within_stack_frames(const void * const stack,
 #ifndef __ASSEMBLY__
 #ifdef CONFIG_COMPAT
 #define TS_I386_REGS_POKED	0x0004	/* regs poked by 32-bit ptracer */
-#define TS_COMPAT_RESTART	0x0008
 
-#define arch_set_restart_data	arch_set_restart_data
+#define arch_set_restart_data(restart)	\
+	do { restart->arch_data = current_thread_info()->status; } while (0)
 
-static inline void arch_set_restart_data(struct restart_block *restart)
-{
-	struct thread_info *ti = current_thread_info();
-	if (ti->status & TS_COMPAT)
-		ti->status |= TS_COMPAT_RESTART;
-	else
-		ti->status &= ~TS_COMPAT_RESTART;
-}
 #endif
 
 #ifdef CONFIG_X86_32
arch/x86/kernel/signal.c +1 -1
index 6c26d2c3a2e4..f306e85a08a6 100644
@@ -767,7 +767,7 @@ handle_signal(struct ksignal *ksig, struct pt_regs *regs)
 static inline unsigned long get_nr_restart_syscall(const struct pt_regs *regs)
 {
 #ifdef CONFIG_IA32_EMULATION
-	if (current_thread_info()->status & TS_COMPAT_RESTART)
+	if (current->restart_block.arch_data & TS_COMPAT)
 		return __NR_ia32_restart_syscall;
 #endif
 #ifdef CONFIG_X86_X32_ABI
include/linux/restart_block.h +1 -0
index bba2920e9c05..980a65594412 100644
@@ -23,6 +23,7 @@ enum timespec_type {
  * System call restart block.
  */
 struct restart_block {
+	unsigned long arch_data;
 	long (*fn)(struct restart_block *);
 	union {
 		/* For futex_wait and futex_wait_requeue_pi */
Signature · 1 line
2.25.1.362.g51ebf55
#12 lore raw
In reply to Oleg Nesterov
Quoted 3 lines · Oleg Nesterov

Somehow I forgot about this problem. Let me resend the last version based on discussion with Linus. IIRC he was agree with this series.

Yeah, looks sane to me.

Linus

#13 lore raw
Quoted 2 lines · Linus Torvalds

Yeah, looks sane to me.

Oh, and in a perfect world we'd have a test for this condition too, no?

Linus

#6 lore raw

Re: [PATCH v4 3/4] x86: introduce TS_COMPAT_RESTART to fix

In reply to Oleg Nesterov
Quoted 13 lines · Oleg Nesterov

The comment in get_nr_restart_syscall() says:

* The problem is that we can get here when ptrace pokes
* syscall-like values into regs even if we're not in a syscall
* at all.

Yes. but if we are not in syscall then the

        status & (TS_COMPAT|TS_I386_REGS_POKED)

check below can't really help:

Quoted 7 lines

- TS_COMPAT can't be set

- TS_I386_REGS_POKED is only set if regs->orig_ax was changed by 32bit debugger; and even in this case get_nr_restart_syscall() is only correct if the tracee is 32bit too.

Suppose that 64bit debugger plays with 32bit tracee and

At the risk of asking an obnoxious question here:

Quoted 5 lines

* Tracee calls sleep(2) // TS_COMPAT is set
* User interrupts the tracee by CTRL-C after 1 sec and does "(gdb) call func()"
* gdb saves the regs by PTRACE_GETREGS

It seems to me that a better solution may be for gdb to see the post-restart-setup state. In other words, shouldn't the GETREGS return with the ax pointing to the restart syscall already?

Now maybe this ship has sailed a long long time ago and we can't do this, but is it at all worth considering?

Quoted 97 lines

* does PTRACE_SETREGS to set %rip='func' and %orig_rax=-1
* PTRACE_CONT // TS_COMPAT is cleared
* func() hits int3.
* Debugger catches SIGTRAP.
* Restore original regs by PTRACE_SETREGS.
* PTRACE_CONT

get_nr_restart_syscall() wrongly returns __NR_restart_syscall==219, the tracee calls ia32_sys_call_table[219] == sys_madvise.

This patch adds the sticky TS_COMPAT_RESTART flag which survives after return to user mode, hopefully it allows us to kill TS_I386_REGS_POKED but this needs another patch.

Test-case:

  $ cvs -d :pserver:anoncvs:[email protected]:/cvs/systemtap co ptrace-tests
  $ gcc -o erestartsys-trap-debuggee ptrace-tests/tests/erestartsys-trap-debuggee.c --m32
  $ gcc -o erestartsys-trap-debugger ptrace-tests/tests/erestartsys-trap-debugger.c -lutil
  $ ./erestartsys-trap-debugger
  Unexpected: retval 1, errno 22
  erestartsys-trap-debugger: ptrace-tests/tests/erestartsys-trap-debugger.c:421
---
 arch/x86/include/asm/thread_info.h | 14 +++++++++++++-
 arch/x86/kernel/signal.c           | 24 +-----------------------
 2 files changed, 14 insertions(+), 24 deletions(-)
arch/x86/include/asm/thread_info.h +13 -1
index c2dc29e215ea..30d1d187019f 100644
@@ -214,10 +214,22 @@ static inline int arch_within_stack_frames(const void * const stack,
  */
 #define TS_COMPAT              0x0002  /* 32bit syscall active (64BIT)*/
 
+#ifndef __ASSEMBLY__
 #ifdef CONFIG_COMPAT
 #define TS_I386_REGS_POKED     0x0004  /* regs poked by 32-bit ptracer */
+#define TS_COMPAT_RESTART      0x0008
+
+#define arch_set_restart_data  arch_set_restart_data
+
+static inline void arch_set_restart_data(struct restart_block *restart)
+{
+       struct thread_info *ti = current_thread_info();
+       if (ti->status & TS_COMPAT)
+               ti->status |= TS_COMPAT_RESTART;
+       else
+               ti->status &= ~TS_COMPAT_RESTART;
+}
 #endif
-#ifndef __ASSEMBLY__
 
 #ifdef CONFIG_X86_32
 #define in_ia32_syscall() true
arch/x86/kernel/signal.c +1 -24
index ea794a083c44..6c26d2c3a2e4 100644
@@ -766,30 +766,8 @@ handle_signal(struct ksignal *ksig, struct pt_regs *regs)
 
 static inline unsigned long get_nr_restart_syscall(const struct pt_regs *regs)
 {
-       /*
-        * This function is fundamentally broken as currently
-        * implemented.
-        *
-        * The idea is that we want to trigger a call to the
-        * restart_block() syscall and that we want in_ia32_syscall(),
-        * in_x32_syscall(), etc. to match whatever they were in the
-        * syscall being restarted.  We assume that the syscall
-        * instruction at (regs->ip - 2) matches whatever syscall
-        * instruction we used to enter in the first place.
-        *
-        * The problem is that we can get here when ptrace pokes
-        * syscall-like values into regs even if we're not in a syscall
-        * at all.
-        *
-        * For now, we maintain historical behavior and guess based on
-        * stored state.  We could do better by saving the actual
-        * syscall arch in restart_block or (with caveats on x32) by
-        * checking if regs->ip points to 'int $0x80'.  The current
-        * behavior is incorrect if a tracer has a different bitness
-        * than the tracee.
-        */
 #ifdef CONFIG_IA32_EMULATION
-       if (current_thread_info()->status & (TS_COMPAT|TS_I386_REGS_POKED))
+       if (current_thread_info()->status & TS_COMPAT_RESTART)
                return __NR_ia32_restart_syscall;
 #endif
 #ifdef CONFIG_X86_X32_ABI
--

2.25.1.362.g51ebf55

#7 lore raw

Re: [PATCH v4 3/4] x86: introduce TS_COMPAT_RESTART to fix

Quoted 26 lines · Andy Lutomirski
Quoted 13 lines · Oleg Nesterov

The comment in get_nr_restart_syscall() says:

* The problem is that we can get here when ptrace pokes
* syscall-like values into regs even if we're not in a syscall
* at all.

Yes. but if we are not in syscall then the

        status & (TS_COMPAT|TS_I386_REGS_POKED)

check below can't really help:

Quoted 7 lines

- TS_COMPAT can't be set

- TS_I386_REGS_POKED is only set if regs->orig_ax was changed by 32bit debugger; and even in this case get_nr_restart_syscall() is only correct if the tracee is 32bit too.

Suppose that 64bit debugger plays with 32bit tracee and

At the risk of asking an obnoxious question here:

Quoted 1 line
Quoted 8 lines
Quoted 4 lines

* Tracee calls sleep(2) // TS_COMPAT is set
* User interrupts the tracee by CTRL-C after 1 sec and does "(gdb) call func()"
* gdb saves the regs by PTRACE_GETREGS

It seems to me that a better solution may be for gdb to see the post-restart-setup state. In other words, shouldn't the GETREGS return with the ax pointing to the restart syscall already?

and ip = regs-ip - 2? And hide ERESTART_BLOCK from debugger? Perhaps I misunderstood, but this doesn't look like a better solution to me. Not to mention this would be the serious user-visible change... And even the necessary changes in getreg() do not look good to me.

Plus I do not understand how this could work. OK, suppose that the tracee reports a signal with ax = ERESTART_BLOCK.

Debugger simply does GETREGS + SETREGS + PTRACE_CONT(signr). In this case handle_signal() should set ax = -EINTR, but syscall_get_error() will report __NR_ia32_restart_syscall?

Probably I greatly misunderstood you...

Oleg.

#14 lore raw
In reply to Linus Torvalds
Quoted 7 lines · Linus Torvalds
Quoted 2 lines · Linus Torvalds

Yeah, looks sane to me.

Oh, and in a perfect world we'd have a test for this condition too, no?

There is the "erestartsys-trap-debugger" test in ptrace-tests suite. Do you mean you want another test in tools/testing/selftests/ptrace ?

Oleg.

#8 lore raw

Re: [PATCH v4 3/4] x86: introduce TS_COMPAT_RESTART to fix

In reply to Oleg Nesterov
Quoted 42 lines · Oleg Nesterov
Quoted 26 lines · Andy Lutomirski
Quoted 13 lines · Oleg Nesterov

The comment in get_nr_restart_syscall() says:

* The problem is that we can get here when ptrace pokes
* syscall-like values into regs even if we're not in a syscall
* at all.

Yes. but if we are not in syscall then the

        status & (TS_COMPAT|TS_I386_REGS_POKED)

check below can't really help:

Quoted 7 lines

- TS_COMPAT can't be set

- TS_I386_REGS_POKED is only set if regs->orig_ax was changed by 32bit debugger; and even in this case get_nr_restart_syscall() is only correct if the tracee is 32bit too.

Suppose that 64bit debugger plays with 32bit tracee and

At the risk of asking an obnoxious question here:

Quoted 1 line
Quoted 8 lines
Quoted 4 lines

* Tracee calls sleep(2) // TS_COMPAT is set
* User interrupts the tracee by CTRL-C after 1 sec and does "(gdb) call func()"
* gdb saves the regs by PTRACE_GETREGS

It seems to me that a better solution may be for gdb to see the post-restart-setup state. In other words, shouldn't the GETREGS return with the ax pointing to the restart syscall already?

and ip = regs-ip - 2? And hide ERESTART_BLOCK from debugger? Perhaps I misunderstood, but this doesn't look like a better solution to me. Not to mention this would be the serious user-visible change... And even the necessary changes in getreg() do not look good to me.

Quoted 8 lines

Plus I do not understand how this could work. OK, suppose that the tracee reports a signal with ax = ERESTART_BLOCK.

Debugger simply does GETREGS + SETREGS + PTRACE_CONT(signr). In this case handle_signal() should set ax = -EINTR, but syscall_get_error() will report __NR_ia32_restart_syscall?

Probably I greatly misunderstood you...

My idea may well be nuts, but I was indeed imagining that we hide ERESTART_BLOCK from the debugger. We would do the ip -= 2 and nr = __NR_..._restart_syscall before any ptrace events happen at all. Admittedly, this may cause strace to fail, so this is probably a bad idea.

Oh well, your patch is probably a decent solution.

#15 lore raw
In reply to Oleg Nesterov
Quoted 3 lines · Oleg Nesterov

There is the "erestartsys-trap-debugger" test in ptrace-tests suite. Do you mean you want another test in tools/testing/selftests/ptrace ?

No, I guess it's fine if it's caught by the ptrace test suite - we'll hopefully get fairly timely "guys, you broke it" reports.

Is that ptrace erestartsys-trap-debugger.c test new, or has it just always failed? Or is the problem that it is so expected to fail that we wouldn't get reports of it anyway (this clearly fell off your radar for a long time)?

IOW, my only worry is that this is somewhat subtle (understatement of the year), has been around forever, and if we care about this debugger case, we should have _some_ way to make sure that if it gets broken again we at least get notified some way in a reasonably timely manner...

Linus

#16 lore raw
Quoted 6 lines · Linus Torvalds
Quoted 3 lines · Oleg Nesterov

There is the "erestartsys-trap-debugger" test in ptrace-tests suite. Do you mean you want another test in tools/testing/selftests/ptrace ?

Quoted 7 lines

No, I guess it's fine if it's caught by the ptrace test suite - we'll hopefully get fairly timely "guys, you broke it" reports.

Is that ptrace erestartsys-trap-debugger.c test new, or has it just always failed? Or is the problem that it is so expected to fail that we wouldn't get reports of it anyway (this clearly fell off your radar for a long time)?

No, this test-case is very old. It used to work when this get_nr_restart_syscall() logic was based on the test_thread_flag(TIF_IA32) check.

Then it started to fail somewhere 2-3 years ago, and to be honest I didn't even try to find which exactly patch broke this test. Because this logic was always wrong anyway, even if this test-case used to work.

I sent v1 soon after this bug was reported, but every time I was too lazy, that is why this (minor) problem is still not fixed. So, in short, this is my fault in any case.

Oleg.

#17 lore raw
In reply to Oleg Nesterov

It seems that nobody objects,

Andrew, Andy, Thomas, how do you think this series should be routed?

Quoted 21 lines · Oleg Nesterov

Somehow I forgot about this problem. Let me resend the last version based on discussion with Linus. IIRC he was agree with this series.

And let me remind why 3/4 temporary adds the "transient" TS_COMPAT_RESTART flag killed by the next patch: to simplify the backporting. 1-3 can fix the problem without breaking the kABI.

Oleg.

---
 arch/x86/include/asm/processor.h   |  9 ---------
 arch/x86/include/asm/thread_info.h | 15 ++++++++++++++-
 arch/x86/kernel/signal.c           | 24 +-----------------------
 fs/select.c                        | 10 ++++------
 include/linux/restart_block.h      |  1 +
 include/linux/thread_info.h        | 13 +++++++++++++
 kernel/futex.c                     |  3 +--
 kernel/time/alarmtimer.c           |  2 +-
 kernel/time/hrtimer.c              |  2 +-
 kernel/time/posix-cpu-timers.c     |  2 +-
 10 files changed, 37 insertions(+), 44 deletions(-)
#18 lore raw
Quoted 4 lines · Oleg Nesterov

It seems that nobody objects,

Andrew, Andy, Thomas, how do you think this series should be routed?

ping...

What can I do to finally get this merged?

Should I resend once again? Whom?

Quoted 22 lines · Oleg Nesterov
Quoted 21 lines

Somehow I forgot about this problem. Let me resend the last version based on discussion with Linus. IIRC he was agree with this series.

And let me remind why 3/4 temporary adds the "transient" TS_COMPAT_RESTART flag killed by the next patch: to simplify the backporting. 1-3 can fix the problem without breaking the kABI.

Oleg.

---
 arch/x86/include/asm/processor.h   |  9 ---------
 arch/x86/include/asm/thread_info.h | 15 ++++++++++++++-
 arch/x86/kernel/signal.c           | 24 +-----------------------
 fs/select.c                        | 10 ++++------
 include/linux/restart_block.h      |  1 +
 include/linux/thread_info.h        | 13 +++++++++++++
 kernel/futex.c                     |  3 +--
 kernel/time/alarmtimer.c           |  2 +-
 kernel/time/hrtimer.c              |  2 +-
 kernel/time/posix-cpu-timers.c     |  2 +-
 10 files changed, 37 insertions(+), 44 deletions(-)
#19 lore raw
Quoted 11 lines · Oleg Nesterov
Quoted 4 lines · Oleg Nesterov

It seems that nobody objects,

Andrew, Andy, Thomas, how do you think this series should be routed?

ping...

What can I do to finally get this merged?

Should I resend once again? Whom?

I'll pick it up then

merge bot Applied
#4 lore raw

[tip: x86/urgent] x86: Move TS_COMPAT back to asm/thread_info.h

In reply to Oleg Nesterov
The following commit has been merged into the x86/urgent branch of tip:

The following commit has been merged into the x86/urgent branch of tip:

Commit-ID:     66c1b6d74cd7035e85c426f0af4aede19e805c8a
Gitweb:        https://git.kernel.org/tip/66c1b6d74cd7035e85c426f0af4aede19e805c8a
Author:        Oleg Nesterov <[email protected]>
AuthorDate:    Mon, 01 Feb 2021 18:46:49 +01:00
Committer:     Thomas Gleixner <[email protected]>
CommitterDate: Tue, 16 Mar 2021 22:13:11 +01:00

x86: Move TS_COMPAT back to asm/thread_info.h

Move TS_COMPAT back to asm/thread_info.h, close to TS_I386_REGS_POKED.

It was moved to asm/processor.h by b9d989c7218a ("x86/asm: Move the thread_info::status field to thread_struct"), then later 37a8f7c38339 ("x86/asm: Move 'status' from thread_struct to thread_info") moved the 'status' field back but TS_COMPAT was forgotten.

Preparatory patch to fix the COMPAT case for get_nr_restart_syscall()

Fixes 609c19a385c8 ("x86/ptrace: Stop setting TS_COMPAT in ptrace code")
---
 arch/x86/include/asm/processor.h   |  9 ---------
 arch/x86/include/asm/thread_info.h |  9 +++++++++
 2 files changed, 9 insertions(+), 9 deletions(-)
arch/x86/include/asm/processor.h +0 -9
index dc6d149..f1b9ed5 100644
@@ -551,15 +551,6 @@ static inline void arch_thread_struct_whitelist(unsigned long *offset,
 	*size = fpu_kernel_xstate_size;
 }
 
-/*
- * Thread-synchronous status.
- *
- * This is different from the flags in that nobody else
- * ever touches our thread-synchronous status, so we don't
- * have to worry about atomic accesses.
- */
-#define TS_COMPAT		0x0002	/* 32bit syscall active (64BIT)*/
-
 static inline void
 native_load_sp0(unsigned long sp0)
 {
arch/x86/include/asm/thread_info.h +9 -0
index 0d751d5..c2dc29e 100644
@@ -205,6 +205,15 @@ static inline int arch_within_stack_frames(const void * const stack,
 
 #endif
 
+/*
+ * Thread-synchronous status.
+ *
+ * This is different from the flags in that nobody else
+ * ever touches our thread-synchronous status, so we don't
+ * have to worry about atomic accesses.
+ */
+#define TS_COMPAT		0x0002	/* 32bit syscall active (64BIT)*/
+
 #ifdef CONFIG_COMPAT
 #define TS_I386_REGS_POKED	0x0004	/* regs poked by 32-bit ptracer */
 #endif
merge bot Applied
#2 lore raw

[tip: x86/urgent] kernel, fs: Introduce and use set_restart_fn() and arch_set_restart_data()

In reply to Oleg Nesterov
The following commit has been merged into the x86/urgent branch of tip:

The following commit has been merged into the x86/urgent branch of tip:

Commit-ID:     5abbe51a526253b9f003e9a0a195638dc882d660
Gitweb:        https://git.kernel.org/tip/5abbe51a526253b9f003e9a0a195638dc882d660
Author:        Oleg Nesterov <[email protected]>
AuthorDate:    Mon, 01 Feb 2021 18:46:41 +01:00
Committer:     Thomas Gleixner <[email protected]>
CommitterDate: Tue, 16 Mar 2021 22:13:10 +01:00

kernel, fs: Introduce and use set_restart_fn() and arch_set_restart_data()

Preparation for fixing get_nr_restart_syscall() on X86 for COMPAT.

Add a new helper which sets restart_block->fn and calls a dummy arch_set_restart_data() helper.

Fixes 609c19a385c8 ("x86/ptrace: Stop setting TS_COMPAT in ptrace code")
---
 fs/select.c                    | 10 ++++------
 include/linux/thread_info.h    | 13 +++++++++++++
 kernel/futex.c                 |  3 +--
 kernel/time/alarmtimer.c       |  2 +-
 kernel/time/hrtimer.c          |  2 +-
 kernel/time/posix-cpu-timers.c |  2 +-
 6 files changed, 21 insertions(+), 11 deletions(-)
fs/select.c +4 -6
index 37aaa83..945896d 100644
@@ -1055,10 +1055,9 @@ static long do_restart_poll(struct restart_block *restart_block)
 
 	ret = do_sys_poll(ufds, nfds, to);
 
-	if (ret == -ERESTARTNOHAND) {
-		restart_block->fn = do_restart_poll;
-		ret = -ERESTART_RESTARTBLOCK;
-	}
+	if (ret == -ERESTARTNOHAND)
+		ret = set_restart_fn(restart_block, do_restart_poll);
+
 	return ret;
 }
 
@@ -1080,7 +1079,6 @@ SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
 		struct restart_block *restart_block;
 
 		restart_block = &current->restart_block;
-		restart_block->fn = do_restart_poll;
 		restart_block->poll.ufds = ufds;
 		restart_block->poll.nfds = nfds;
 
@@ -1091,7 +1089,7 @@ SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
 		} else
 			restart_block->poll.has_timeout = 0;
 
-		ret = -ERESTART_RESTARTBLOCK;
+		ret = set_restart_fn(restart_block, do_restart_poll);
 	}
 	return ret;
 }
include/linux/thread_info.h +13 -0
index 9b2158c..157762d 100644
@@ -11,6 +11,7 @@
 #include <linux/types.h>
 #include <linux/bug.h>
 #include <linux/restart_block.h>
+#include <linux/errno.h>
 
 #ifdef CONFIG_THREAD_INFO_IN_TASK
 /*
@@ -59,6 +60,18 @@ enum syscall_work_bit {
 
 #ifdef __KERNEL__
 
+#ifndef arch_set_restart_data
+#define arch_set_restart_data(restart) do { } while (0)
+#endif
+
+static inline long set_restart_fn(struct restart_block *restart,
+					long (*fn)(struct restart_block *))
+{
+	restart->fn = fn;
+	arch_set_restart_data(restart);
+	return -ERESTART_RESTARTBLOCK;
+}
+
 #ifndef THREAD_ALIGN
 #define THREAD_ALIGN	THREAD_SIZE
 #endif
kernel/futex.c +1 -2
index e68db77..00febd6 100644
@@ -2728,14 +2728,13 @@ retry:
 		goto out;
 
 	restart = &current->restart_block;
-	restart->fn = futex_wait_restart;
 	restart->futex.uaddr = uaddr;
 	restart->futex.val = val;
 	restart->futex.time = *abs_time;
 	restart->futex.bitset = bitset;
 	restart->futex.flags = flags | FLAGS_HAS_TIMEOUT;
 
-	ret = -ERESTART_RESTARTBLOCK;
+	ret = set_restart_fn(restart, futex_wait_restart);
 
 out:
 	if (to) {
kernel/time/alarmtimer.c +1 -1
index 98d7a15..4d94e2b 100644
@@ -854,9 +854,9 @@ static int alarm_timer_nsleep(const clockid_t which_clock, int flags,
 	if (flags == TIMER_ABSTIME)
 		return -ERESTARTNOHAND;
 
-	restart->fn = alarm_timer_nsleep_restart;
 	restart->nanosleep.clockid = type;
 	restart->nanosleep.expires = exp;
+	set_restart_fn(restart, alarm_timer_nsleep_restart);
 	return ret;
 }
 
kernel/time/hrtimer.c +1 -1
index 788b9d1..5c9d968 100644
@@ -1957,9 +1957,9 @@ long hrtimer_nanosleep(ktime_t rqtp, const enum hrtimer_mode mode,
 	}
 
 	restart = &current->restart_block;
-	restart->fn = hrtimer_nanosleep_restart;
 	restart->nanosleep.clockid = t.timer.base->clockid;
 	restart->nanosleep.expires = hrtimer_get_expires_tv64(&t.timer);
+	set_restart_fn(restart, hrtimer_nanosleep_restart);
 out:
 	destroy_hrtimer_on_stack(&t.timer);
 	return ret;
kernel/time/posix-cpu-timers.c +1 -1
index a71758e..9abe152 100644
@@ -1480,8 +1480,8 @@ static int posix_cpu_nsleep(const clockid_t which_clock, int flags,
 		if (flags & TIMER_ABSTIME)
 			return -ERESTARTNOHAND;
 
-		restart_block->fn = posix_cpu_nsleep_restart;
 		restart_block->nanosleep.clockid = which_clock;
+		set_restart_fn(restart_block, posix_cpu_nsleep_restart);
 	}
 	return error;
 }
merge bot Applied
#9 lore raw

[tip: x86/urgent] x86: Introduce TS_COMPAT_RESTART to fix get_nr_restart_syscall()

In reply to Oleg Nesterov
The following commit has been merged into the x86/urgent branch of tip:

The following commit has been merged into the x86/urgent branch of tip:

Commit-ID:     8c150ba2fb5995c84a7a43848250d444a3329a7d
Gitweb:        https://git.kernel.org/tip/8c150ba2fb5995c84a7a43848250d444a3329a7d
Author:        Oleg Nesterov <[email protected]>
AuthorDate:    Mon, 01 Feb 2021 18:47:09 +01:00
Committer:     Thomas Gleixner <[email protected]>
CommitterDate: Tue, 16 Mar 2021 22:13:11 +01:00

x86: Introduce TS_COMPAT_RESTART to fix get_nr_restart_syscall()

The comment in get_nr_restart_syscall() says:

* The problem is that we can get here when ptrace pokes
* syscall-like values into regs even if we're not in a syscall
* at all.

Yes, but if not in a syscall then the

	status & (TS_COMPAT|TS_I386_REGS_POKED)

check below can't really help:

- TS_COMPAT can't be set

- TS_I386_REGS_POKED is only set if regs->orig_ax was changed by 32bit debugger; and even in this case get_nr_restart_syscall() is only correct if the tracee is 32bit too.

Suppose that a 64bit debugger plays with a 32bit tracee and

* Tracee calls sleep(2) // TS_COMPAT is set
* User interrupts the tracee by CTRL-C after 1 sec and does "(gdb) call func()"
* gdb saves the regs by PTRACE_GETREGS
* does PTRACE_SETREGS to set %rip='func' and %orig_rax=-1
* PTRACE_CONT // TS_COMPAT is cleared
* func() hits int3.
* Debugger catches SIGTRAP.
* Restore original regs by PTRACE_SETREGS.
* PTRACE_CONT

get_nr_restart_syscall() wrongly returns __NR_restart_syscall==219, the tracee calls ia32_sys_call_table[219] == sys_madvise.

Add the sticky TS_COMPAT_RESTART flag which survives after return to user mode. It's going to be removed in the next step again by storing the information in the restart block. As a further cleanup it might be possible to remove also TS_I386_REGS_POKED with that.

Test-case:

  $ cvs -d :pserver:anoncvs:[email protected]:/cvs/systemtap co ptrace-tests
  $ gcc -o erestartsys-trap-debuggee ptrace-tests/tests/erestartsys-trap-debuggee.c --m32
  $ gcc -o erestartsys-trap-debugger ptrace-tests/tests/erestartsys-trap-debugger.c -lutil
  $ ./erestartsys-trap-debugger
  Unexpected: retval 1, errno 22
  erestartsys-trap-debugger: ptrace-tests/tests/erestartsys-trap-debugger.c:421
Fixes 609c19a385c8 ("x86/ptrace: Stop setting TS_COMPAT in ptrace code")
---
 arch/x86/include/asm/thread_info.h | 14 +++++++++++++-
 arch/x86/kernel/signal.c           | 24 +-----------------------
 2 files changed, 14 insertions(+), 24 deletions(-)
arch/x86/include/asm/thread_info.h +13 -1
index c2dc29e..30d1d18 100644
@@ -214,10 +214,22 @@ static inline int arch_within_stack_frames(const void * const stack,
  */
 #define TS_COMPAT		0x0002	/* 32bit syscall active (64BIT)*/
 
+#ifndef __ASSEMBLY__
 #ifdef CONFIG_COMPAT
 #define TS_I386_REGS_POKED	0x0004	/* regs poked by 32-bit ptracer */
+#define TS_COMPAT_RESTART	0x0008
+
+#define arch_set_restart_data	arch_set_restart_data
+
+static inline void arch_set_restart_data(struct restart_block *restart)
+{
+	struct thread_info *ti = current_thread_info();
+	if (ti->status & TS_COMPAT)
+		ti->status |= TS_COMPAT_RESTART;
+	else
+		ti->status &= ~TS_COMPAT_RESTART;
+}
 #endif
-#ifndef __ASSEMBLY__
 
 #ifdef CONFIG_X86_32
 #define in_ia32_syscall() true
arch/x86/kernel/signal.c +1 -23
index ea794a0..6c26d2c 100644
@@ -766,30 +766,8 @@ handle_signal(struct ksignal *ksig, struct pt_regs *regs)
 
 static inline unsigned long get_nr_restart_syscall(const struct pt_regs *regs)
 {
-	/*
-	 * This function is fundamentally broken as currently
-	 * implemented.
-	 *
-	 * The idea is that we want to trigger a call to the
-	 * restart_block() syscall and that we want in_ia32_syscall(),
-	 * in_x32_syscall(), etc. to match whatever they were in the
-	 * syscall being restarted.  We assume that the syscall
-	 * instruction at (regs->ip - 2) matches whatever syscall
-	 * instruction we used to enter in the first place.
-	 *
-	 * The problem is that we can get here when ptrace pokes
-	 * syscall-like values into regs even if we're not in a syscall
-	 * at all.
-	 *
-	 * For now, we maintain historical behavior and guess based on
-	 * stored state.  We could do better by saving the actual
-	 * syscall arch in restart_block or (with caveats on x32) by
-	 * checking if regs->ip points to 'int $0x80'.  The current
-	 * behavior is incorrect if a tracer has a different bitness
-	 * than the tracee.
-	 */
 #ifdef CONFIG_IA32_EMULATION
-	if (current_thread_info()->status & (TS_COMPAT|TS_I386_REGS_POKED))
+	if (current_thread_info()->status & TS_COMPAT_RESTART)
 		return __NR_ia32_restart_syscall;
 #endif
 #ifdef CONFIG_X86_X32_ABI
merge bot Applied
#11 lore raw

[tip: x86/urgent] x86: Introduce restart_block->arch_data to remove TS_COMPAT_RESTART

In reply to Oleg Nesterov
The following commit has been merged into the x86/urgent branch of tip:

The following commit has been merged into the x86/urgent branch of tip:

Commit-ID:     b2e9df850c58c2b36e915e7d3bed3f6107cccba6
Gitweb:        https://git.kernel.org/tip/b2e9df850c58c2b36e915e7d3bed3f6107cccba6
Author:        Oleg Nesterov <[email protected]>
AuthorDate:    Mon, 01 Feb 2021 18:47:16 +01:00
Committer:     Thomas Gleixner <[email protected]>
CommitterDate: Tue, 16 Mar 2021 22:13:11 +01:00

x86: Introduce restart_block->arch_data to remove TS_COMPAT_RESTART

Save the current_thread_info()->status of X86 in the new restart_block->arch_data field so TS_COMPAT_RESTART can be removed again.

---
 arch/x86/include/asm/thread_info.h | 12 ++----------
 arch/x86/kernel/signal.c           |  2 +-
 include/linux/restart_block.h      |  1 +
 3 files changed, 4 insertions(+), 11 deletions(-)
arch/x86/include/asm/thread_info.h +2 -10
index 30d1d18..06b740b 100644
@@ -217,18 +217,10 @@ static inline int arch_within_stack_frames(const void * const stack,
 #ifndef __ASSEMBLY__
 #ifdef CONFIG_COMPAT
 #define TS_I386_REGS_POKED	0x0004	/* regs poked by 32-bit ptracer */
-#define TS_COMPAT_RESTART	0x0008
 
-#define arch_set_restart_data	arch_set_restart_data
+#define arch_set_restart_data(restart)	\
+	do { restart->arch_data = current_thread_info()->status; } while (0)
 
-static inline void arch_set_restart_data(struct restart_block *restart)
-{
-	struct thread_info *ti = current_thread_info();
-	if (ti->status & TS_COMPAT)
-		ti->status |= TS_COMPAT_RESTART;
-	else
-		ti->status &= ~TS_COMPAT_RESTART;
-}
 #endif
 
 #ifdef CONFIG_X86_32
arch/x86/kernel/signal.c +1 -1
index 6c26d2c..f306e85 100644
@@ -767,7 +767,7 @@ handle_signal(struct ksignal *ksig, struct pt_regs *regs)
 static inline unsigned long get_nr_restart_syscall(const struct pt_regs *regs)
 {
 #ifdef CONFIG_IA32_EMULATION
-	if (current_thread_info()->status & TS_COMPAT_RESTART)
+	if (current->restart_block.arch_data & TS_COMPAT)
 		return __NR_ia32_restart_syscall;
 #endif
 #ifdef CONFIG_X86_X32_ABI
include/linux/restart_block.h +1 -0
index bba2920..980a655 100644
@@ -23,6 +23,7 @@ enum timespec_type {
  * System call restart block.
  */
 struct restart_block {
+	unsigned long arch_data;
 	long (*fn)(struct restart_block *);
 	union {
 		/* For futex_wait and futex_wait_requeue_pi */