[PATCH RFC] x86/asm: Introduce static_retcall(s)

From: Andrei Vagin
Date: Wed Mar 27 2019 - 15:22:25 EST


From: Dmitry Safonov <dima@xxxxxxxxxx>

Provide framework to overwrite tail call in a function with return.

XXX: split vdso/generic part

Signed-off-by: Dmitry Safonov <dima@xxxxxxxxxx>
Signed-off-by: Andrei Vagin <avagin@xxxxxxxxx>
---
arch/x86/entry/vdso/vclock_gettime.c | 19 ++++++----
arch/x86/entry/vdso/vdso-layout.lds.S | 1 +
arch/x86/entry/vdso/vdso2c.h | 11 +++++-
arch/x86/entry/vdso/vma.c | 22 +++++++++++
arch/x86/include/asm/static_retcall.h | 54 +++++++++++++++++++++++++++
arch/x86/include/asm/vdso.h | 1 +
6 files changed, 99 insertions(+), 9 deletions(-)
create mode 100644 arch/x86/include/asm/static_retcall.h

diff --git a/arch/x86/entry/vdso/vclock_gettime.c b/arch/x86/entry/vdso/vclock_gettime.c
index cb55bd994497..9416f1ee6b73 100644
--- a/arch/x86/entry/vdso/vclock_gettime.c
+++ b/arch/x86/entry/vdso/vclock_gettime.c
@@ -18,6 +18,7 @@
#include <asm/msr.h>
#include <asm/pvclock.h>
#include <asm/mshyperv.h>
+#include <asm/static_retcall.h>
#include <linux/math64.h>
#include <linux/time.h>
#include <linux/kernel.h>
@@ -39,7 +40,7 @@ extern u8 hvclock_page
__attribute__((visibility("hidden")));
#endif

-#ifdef BUILD_VDSO_TIME_NS
+#ifdef CONFIG_TIME_NS
extern u8 timens_page
__attribute__((visibility("hidden")));
#endif
@@ -145,9 +146,9 @@ notrace static inline u64 vgetcyc(int mode)
return U64_MAX;
}

+#ifdef CONFIG_TIME_NS
notrace static __always_inline void clk_to_ns(clockid_t clk, struct timespec *ts)
{
-#ifdef BUILD_VDSO_TIME_NS
struct timens_offsets *timens = (struct timens_offsets *) &timens_page;
struct timespec64 *offset64;

@@ -173,9 +174,13 @@ notrace static __always_inline void clk_to_ns(clockid_t clk, struct timespec *ts
ts->tv_nsec += NSEC_PER_SEC;
ts->tv_sec--;
}
-
-#endif
}
+#define _static_retcall static_retcall
+#define _static_retcall_int static_retcall_int
+#else
+#define _static_retcall(...)
+#define _static_retcall_int(...)
+#endif

notrace static int do_hres(clockid_t clk, struct timespec *ts)
{
@@ -203,9 +208,7 @@ notrace static int do_hres(clockid_t clk, struct timespec *ts)
ts->tv_sec = sec + __iter_div_u64_rem(ns, NSEC_PER_SEC, &ns);
ts->tv_nsec = ns;

- clk_to_ns(clk, ts);
-
- return 0;
+ _static_retcall_int(0, clk_to_ns, clk, ts);
}

notrace static void do_coarse(clockid_t clk, struct timespec *ts)
@@ -219,7 +222,7 @@ notrace static void do_coarse(clockid_t clk, struct timespec *ts)
ts->tv_nsec = base->nsec;
} while (unlikely(gtod_read_retry(gtod, seq)));

- clk_to_ns(clk, ts);
+ _static_retcall(clk_to_ns, clk, ts);
}

notrace int __vdso_clock_gettime(clockid_t clock, struct timespec *ts)
diff --git a/arch/x86/entry/vdso/vdso-layout.lds.S b/arch/x86/entry/vdso/vdso-layout.lds.S
index ba216527e59f..075cae6f33bf 100644
--- a/arch/x86/entry/vdso/vdso-layout.lds.S
+++ b/arch/x86/entry/vdso/vdso-layout.lds.S
@@ -45,6 +45,7 @@ SECTIONS
.gnu.version : { *(.gnu.version) }
.gnu.version_d : { *(.gnu.version_d) }
.gnu.version_r : { *(.gnu.version_r) }
+ __retcall_table : { *(__retcall_table) } :text

.dynamic : { *(.dynamic) } :text :dynamic

diff --git a/arch/x86/entry/vdso/vdso2c.h b/arch/x86/entry/vdso/vdso2c.h
index 660f725a02c1..ae91567fd567 100644
--- a/arch/x86/entry/vdso/vdso2c.h
+++ b/arch/x86/entry/vdso/vdso2c.h
@@ -16,7 +16,7 @@ static void BITSFUNC(go)(void *raw_addr, size_t raw_len,
unsigned int i, syms_nr;
unsigned long j;
ELF(Shdr) *symtab_hdr = NULL, *strtab_hdr, *secstrings_hdr,
- *alt_sec = NULL;
+ *alt_sec = NULL, *retcall_sec = NULL;
ELF(Dyn) *dyn = 0, *dyn_end = 0;
const char *secstrings;
INT_BITS syms[NSYMS] = {};
@@ -78,6 +78,9 @@ static void BITSFUNC(go)(void *raw_addr, size_t raw_len,
if (!strcmp(secstrings + GET_LE(&sh->sh_name),
".altinstructions"))
alt_sec = sh;
+ if (!strcmp(secstrings + GET_LE(&sh->sh_name),
+ "__retcall_table"))
+ retcall_sec = sh;
}

if (!symtab_hdr)
@@ -165,6 +168,12 @@ static void BITSFUNC(go)(void *raw_addr, size_t raw_len,
fprintf(outfile, "\t.alt_len = %lu,\n",
(unsigned long)GET_LE(&alt_sec->sh_size));
}
+ if (retcall_sec) {
+ fprintf(outfile, "\t.retcall = %lu,\n",
+ (unsigned long)GET_LE(&retcall_sec->sh_offset));
+ fprintf(outfile, "\t.retcall_len = %lu,\n",
+ (unsigned long)GET_LE(&retcall_sec->sh_size));
+ }
for (i = 0; i < NSYMS; i++) {
if (required_syms[i].export && syms[i])
fprintf(outfile, "\t.sym_%s = %" PRIi64 ",\n",
diff --git a/arch/x86/entry/vdso/vma.c b/arch/x86/entry/vdso/vma.c
index 0b8d9f6f0ce3..b4ea7a2ebfed 100644
--- a/arch/x86/entry/vdso/vma.c
+++ b/arch/x86/entry/vdso/vma.c
@@ -25,6 +25,7 @@
#include <asm/cpufeature.h>
#include <asm/mshyperv.h>
#include <asm/page.h>
+#include <asm/static_retcall.h>
#include <asm/tlb.h>

#if defined(CONFIG_X86_64)
@@ -38,6 +39,25 @@ static __init int vdso_setup(char *s)
__setup("vdso=", vdso_setup);
#endif

+static __init int apply_retcalls(struct retcall_entry *ent, unsigned long nr)
+{
+ while (nr--) {
+ void *call_addr = (void *)ent + ent->call;
+ void *ret_addr = (void *)ent + ent->ret;
+ size_t ret_sz = ent->out - ent->ret;
+
+ if (WARN_ON(ret_sz > PAGE_SIZE))
+ goto next;
+
+ memcpy(call_addr, ret_addr, ret_sz);
+
+next:
+ ent++;
+ }
+
+ return 0;
+}
+
void __init init_vdso_image(struct vdso_image *image)
{
BUG_ON(image->size % PAGE_SIZE != 0);
@@ -51,6 +71,8 @@ void __init init_vdso_image(struct vdso_image *image)
return;

memcpy(image->text_timens, image->text, image->size);
+ apply_retcalls((struct retcall_entry *)(image->text + image->retcall),
+ image->retcall_len / sizeof(struct retcall_entry));
#endif
}

diff --git a/arch/x86/include/asm/static_retcall.h b/arch/x86/include/asm/static_retcall.h
new file mode 100644
index 000000000000..fdb13795b74d
--- /dev/null
+++ b/arch/x86/include/asm/static_retcall.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (C) 2019 Dmitry Safonov, Andrey Vagin
+ */
+
+#ifndef _ASM_X86_STATIC_RETCALL_H
+#define _ASM_X86_STATIC_RETCALL_H
+
+struct retcall_entry {
+ u16 call;
+ u16 ret;
+ u16 out;
+};
+
+#define static_retcall(func, ...) \
+ do { \
+ asm_volatile_goto( \
+ ".pushsection __retcall_table, \"aw\" \n\t" \
+ "2: .word %l[l_call] - 2b\n\t" \
+ ".word %l[l_return] - 2b\n\t" \
+ ".word %l[l_out] - 2b\n\t" \
+ ".popsection" \
+ : : : : l_call, l_return, l_out); \
+l_call: \
+ func(__VA_ARGS__); \
+l_return: \
+ return; \
+ annotate_reachable(); \
+l_out: \
+ nop(); \
+ return; \
+ } while(0)
+
+#define static_retcall_int(ret, func, ...) \
+ do { \
+ asm_volatile_goto( \
+ ".pushsection __retcall_table, \"aw\" \n\t" \
+ _ASM_ALIGN "\n\t" \
+ "2: .word %l[l_call] - 2b\n\t" \
+ ".word %l[l_return] - 2b\n\t" \
+ ".word %l[l_out] - 2b\n\t" \
+ ".popsection" \
+ : : : : l_call, l_return, l_out); \
+l_call: \
+ func(__VA_ARGS__); \
+l_return: \
+ return ret; \
+ annotate_reachable(); \
+l_out: \
+ nop(); \
+ return ret; \
+ } while(0)
+
+#endif
diff --git a/arch/x86/include/asm/vdso.h b/arch/x86/include/asm/vdso.h
index 583133446874..acdf70bf814b 100644
--- a/arch/x86/include/asm/vdso.h
+++ b/arch/x86/include/asm/vdso.h
@@ -16,6 +16,7 @@ struct vdso_image {
unsigned long size; /* Always a multiple of PAGE_SIZE */

unsigned long alt, alt_len;
+ unsigned long retcall, retcall_len;

long sym_vvar_start; /* Negative offset to the vvar area */

--
2.20.1