Jiri Slaby a2e715
From: Peter Zijlstra <peterz@infradead.org>
Jiri Slaby a2e715
Date: Wed, 8 Feb 2023 18:10:52 +0100
Jiri Slaby a2e715
Subject: x86/alternative: Support relocations in alternatives
Jiri Slaby a2e715
Git-commit: 270a69c4485d7d07516d058bcc0473c90ee22185
Jiri Slaby a2e715
Git-repo: https://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git#master
Jiri Slaby a2e715
Patch-mainline: Queued in subsystem maintainer repository
Jiri Slaby a2e715
References: bsc#1206578
Jiri Slaby a2e715
Jiri Slaby a2e715
A little while ago someone (Kirill) ran into the whole 'alternatives don't
Jiri Slaby a2e715
do relocations nonsense' again and I got annoyed enough to actually look
Jiri Slaby a2e715
at the code.
Jiri Slaby a2e715
Jiri Slaby a2e715
Since the whole alternative machinery already fully decodes the
Jiri Slaby a2e715
instructions it is simple enough to adjust immediates and displacement
Jiri Slaby a2e715
when needed. Specifically, the immediates for IP modifying instructions
Jiri Slaby a2e715
(JMP, CALL, Jcc) and the displacement for RIP-relative instructions.
Jiri Slaby a2e715
Jiri Slaby a2e715
  [ bp: Massage comment some more and get rid of third loop in
Jiri Slaby a2e715
    apply_relocation(). ]
Jiri Slaby a2e715
Jiri Slaby a2e715
[js] declare 'next, i' out of 'for' loop.
Jiri Slaby a2e715
Jiri Slaby a2e715
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Jiri Slaby a2e715
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Jiri Slaby a2e715
Link: https://lore.kernel.org/r/20230208171431.313857925@infradead.org
Jiri Slaby a2e715
Signed-off-by: Jiri Slaby <jslaby@suse.cz>
Jiri Slaby a2e715
---
Jiri Slaby a2e715
 arch/x86/kernel/alternative.c    |  261 +++++++++++++++++++++++++--------------
Jiri Slaby a2e715
 tools/objtool/arch/x86/special.c |    8 -
Jiri Slaby a2e715
 2 files changed, 173 insertions(+), 96 deletions(-)
Jiri Slaby a2e715
Jiri Slaby a2e715
--- a/arch/x86/kernel/alternative.c
Jiri Slaby a2e715
+++ b/arch/x86/kernel/alternative.c
Jiri Slaby a2e715
@@ -133,71 +133,6 @@ extern s32 __smp_locks[], __smp_locks_en
Jiri Slaby a2e715
 void text_poke_early(void *addr, const void *opcode, size_t len);
Jiri Slaby a2e715
 
Jiri Slaby a2e715
 /*
Jiri Slaby a2e715
- * Are we looking at a near JMP with a 1 or 4-byte displacement.
Jiri Slaby a2e715
- */
Jiri Slaby a2e715
-static inline bool is_jmp(const u8 opcode)
Jiri Slaby a2e715
-{
Jiri Slaby a2e715
-	return opcode == 0xeb || opcode == 0xe9;
Jiri Slaby a2e715
-}
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-static void __init_or_module
Jiri Slaby a2e715
-recompute_jump(struct alt_instr *a, u8 *orig_insn, u8 *repl_insn, u8 *insn_buff)
Jiri Slaby a2e715
-{
Jiri Slaby a2e715
-	u8 *next_rip, *tgt_rip;
Jiri Slaby a2e715
-	s32 n_dspl, o_dspl;
Jiri Slaby a2e715
-	int repl_len;
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-	if (a->replacementlen != 5)
Jiri Slaby a2e715
-		return;
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-	o_dspl = *(s32 *)(insn_buff + 1);
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-	/* next_rip of the replacement JMP */
Jiri Slaby a2e715
-	next_rip = repl_insn + a->replacementlen;
Jiri Slaby a2e715
-	/* target rip of the replacement JMP */
Jiri Slaby a2e715
-	tgt_rip  = next_rip + o_dspl;
Jiri Slaby a2e715
-	n_dspl = tgt_rip - orig_insn;
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-	DPRINTK(ALT, "target RIP: %px, new_displ: 0x%x", tgt_rip, n_dspl);
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-	if (tgt_rip - orig_insn >= 0) {
Jiri Slaby a2e715
-		if (n_dspl - 2 <= 127)
Jiri Slaby a2e715
-			goto two_byte_jmp;
Jiri Slaby a2e715
-		else
Jiri Slaby a2e715
-			goto five_byte_jmp;
Jiri Slaby a2e715
-	/* negative offset */
Jiri Slaby a2e715
-	} else {
Jiri Slaby a2e715
-		if (((n_dspl - 2) & 0xff) == (n_dspl - 2))
Jiri Slaby a2e715
-			goto two_byte_jmp;
Jiri Slaby a2e715
-		else
Jiri Slaby a2e715
-			goto five_byte_jmp;
Jiri Slaby a2e715
-	}
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-two_byte_jmp:
Jiri Slaby a2e715
-	n_dspl -= 2;
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-	insn_buff[0] = 0xeb;
Jiri Slaby a2e715
-	insn_buff[1] = (s8)n_dspl;
Jiri Slaby a2e715
-	add_nops(insn_buff + 2, 3);
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-	repl_len = 2;
Jiri Slaby a2e715
-	goto done;
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-five_byte_jmp:
Jiri Slaby a2e715
-	n_dspl -= 5;
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-	insn_buff[0] = 0xe9;
Jiri Slaby a2e715
-	*(s32 *)&insn_buff[1] = n_dspl;
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-	repl_len = 5;
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-done:
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-	DPRINTK(ALT, "final displ: 0x%08x, JMP 0x%lx",
Jiri Slaby a2e715
-		n_dspl, (unsigned long)orig_insn + n_dspl + repl_len);
Jiri Slaby a2e715
-}
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-/*
Jiri Slaby a2e715
  * optimize_nops_range() - Optimize a sequence of single byte NOPs (0x90)
Jiri Slaby a2e715
  *
Jiri Slaby a2e715
  * @instr: instruction byte stream
Jiri Slaby a2e715
@@ -264,6 +199,140 @@ static void __init_or_module noinline op
Jiri Slaby a2e715
 }
Jiri Slaby a2e715
 
Jiri Slaby a2e715
 /*
Jiri Slaby a2e715
+ * In this context, "source" is where the instructions are placed in the
Jiri Slaby a2e715
+ * section .altinstr_replacement, for example during kernel build by the
Jiri Slaby a2e715
+ * toolchain.
Jiri Slaby a2e715
+ * "Destination" is where the instructions are being patched in by this
Jiri Slaby a2e715
+ * machinery.
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ * The source offset is:
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ *   src_imm = target - src_next_ip                  (1)
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ * and the target offset is:
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ *   dst_imm = target - dst_next_ip                  (2)
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ * so rework (1) as an expression for target like:
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ *   target = src_imm + src_next_ip                  (1a)
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ * and substitute in (2) to get:
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ *   dst_imm = (src_imm + src_next_ip) - dst_next_ip (3)
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ * Now, since the instruction stream is 'identical' at src and dst (it
Jiri Slaby a2e715
+ * is being copied after all) it can be stated that:
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ *   src_next_ip = src + ip_offset
Jiri Slaby a2e715
+ *   dst_next_ip = dst + ip_offset                   (4)
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ * Substitute (4) in (3) and observe ip_offset being cancelled out to
Jiri Slaby a2e715
+ * obtain:
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ *   dst_imm = src_imm + (src + ip_offset) - (dst + ip_offset)
Jiri Slaby a2e715
+ *           = src_imm + src - dst + ip_offset - ip_offset
Jiri Slaby a2e715
+ *           = src_imm + src - dst                   (5)
Jiri Slaby a2e715
+ *
Jiri Slaby a2e715
+ * IOW, only the relative displacement of the code block matters.
Jiri Slaby a2e715
+ */
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+#define apply_reloc_n(n_, p_, d_)				\
Jiri Slaby a2e715
+	do {							\
Jiri Slaby a2e715
+		s32 v = *(s##n_ *)(p_);				\
Jiri Slaby a2e715
+		v += (d_);					\
Jiri Slaby a2e715
+		BUG_ON((v >> 31) != (v >> (n_-1)));		\
Jiri Slaby a2e715
+		*(s##n_ *)(p_) = (s##n_)v;			\
Jiri Slaby a2e715
+	} while (0)
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+static __always_inline
Jiri Slaby a2e715
+void apply_reloc(int n, void *ptr, uintptr_t diff)
Jiri Slaby a2e715
+{
Jiri Slaby a2e715
+	switch (n) {
Jiri Slaby a2e715
+	case 1: apply_reloc_n(8, ptr, diff); break;
Jiri Slaby a2e715
+	case 2: apply_reloc_n(16, ptr, diff); break;
Jiri Slaby a2e715
+	case 4: apply_reloc_n(32, ptr, diff); break;
Jiri Slaby a2e715
+	default: BUG();
Jiri Slaby a2e715
+	}
Jiri Slaby a2e715
+}
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+static __always_inline
Jiri Slaby a2e715
+bool need_reloc(unsigned long offset, u8 *src, size_t src_len)
Jiri Slaby a2e715
+{
Jiri Slaby a2e715
+	u8 *target = src + offset;
Jiri Slaby a2e715
+	/*
Jiri Slaby a2e715
+	 * If the target is inside the patched block, it's relative to the
Jiri Slaby a2e715
+	 * block itself and does not need relocation.
Jiri Slaby a2e715
+	 */
Jiri Slaby a2e715
+	return (target < src || target > src + src_len);
Jiri Slaby a2e715
+}
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+static void __init_or_module noinline
Jiri Slaby a2e715
+apply_relocation(u8 *buf, size_t len, u8 *dest, u8 *src, size_t src_len)
Jiri Slaby a2e715
+{
Jiri Slaby a2e715
+	int next, i = 0;
Jiri Slaby a2e715
+	for (; i < len; i = next) {
Jiri Slaby a2e715
+		struct insn insn;
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+		if (WARN_ON_ONCE(insn_decode_kernel(&insn, &buf[i])))
Jiri Slaby a2e715
+			return;
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+		next = i + insn.length;
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+		switch (insn.opcode.bytes[0]) {
Jiri Slaby a2e715
+		case 0x0f:
Jiri Slaby a2e715
+			if (insn.opcode.bytes[1] < 0x80 ||
Jiri Slaby a2e715
+			    insn.opcode.bytes[1] > 0x8f)
Jiri Slaby a2e715
+				break;
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+			fallthrough;	/* Jcc.d32 */
Jiri Slaby a2e715
+		case 0x70 ... 0x7f:	/* Jcc.d8 */
Jiri Slaby a2e715
+		case JMP8_INSN_OPCODE:
Jiri Slaby a2e715
+		case JMP32_INSN_OPCODE:
Jiri Slaby a2e715
+		case CALL_INSN_OPCODE:
Jiri Slaby a2e715
+			if (need_reloc(next + insn.immediate.value, src, src_len)) {
Jiri Slaby a2e715
+				apply_reloc(insn.immediate.nbytes,
Jiri Slaby a2e715
+					    buf + i + insn_offset_immediate(&insn),
Jiri Slaby a2e715
+					    src - dest);
Jiri Slaby a2e715
+			}
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+			/*
Jiri Slaby a2e715
+			 * Where possible, convert JMP.d32 into JMP.d8.
Jiri Slaby a2e715
+			 */
Jiri Slaby a2e715
+			if (insn.opcode.bytes[0] == JMP32_INSN_OPCODE) {
Jiri Slaby a2e715
+				s32 imm = insn.immediate.value;
Jiri Slaby a2e715
+				imm += src - dest;
Jiri Slaby a2e715
+				imm += JMP32_INSN_SIZE - JMP8_INSN_SIZE;
Jiri Slaby a2e715
+				if ((imm >> 31) == (imm >> 7)) {
Jiri Slaby a2e715
+					buf[i+0] = JMP8_INSN_OPCODE;
Jiri Slaby a2e715
+					buf[i+1] = (s8)imm;
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+					memset(&buf[i+2], INT3_INSN_OPCODE, insn.length - 2);
Jiri Slaby a2e715
+				}
Jiri Slaby a2e715
+			}
Jiri Slaby a2e715
+			break;
Jiri Slaby a2e715
+		}
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+		if (insn_rip_relative(&insn)) {
Jiri Slaby a2e715
+			if (need_reloc(next + insn.displacement.value, src, src_len)) {
Jiri Slaby a2e715
+				apply_reloc(insn.displacement.nbytes,
Jiri Slaby a2e715
+					    buf + i + insn_offset_displacement(&insn),
Jiri Slaby a2e715
+					    src - dest);
Jiri Slaby a2e715
+			}
Jiri Slaby a2e715
+		}
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+		/*
Jiri Slaby a2e715
+		 * See if this and any potentially following NOPs can be
Jiri Slaby a2e715
+		 * optimized.
Jiri Slaby a2e715
+		 */
Jiri Slaby a2e715
+		if (insn.length == 1 && insn.opcode.bytes[0] == 0x90)
Jiri Slaby a2e715
+			next = i + optimize_nops_range(buf, len, i);
Jiri Slaby a2e715
+	}
Jiri Slaby a2e715
+}
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+/*
Jiri Slaby a2e715
  * Replace instructions with better alternatives for this CPU type. This runs
Jiri Slaby a2e715
  * before SMP is initialized to avoid SMP problems with self modifying code.
Jiri Slaby a2e715
  * This implies that asymmetric systems where APs have less capabilities than
Jiri Slaby a2e715
@@ -306,8 +374,10 @@ void __init_or_module noinline apply_alt
Jiri Slaby a2e715
 		 * - feature not present but ALTINSTR_FLAG_INV is set to mean,
Jiri Slaby a2e715
 		 *   patch if feature is *NOT* present.
Jiri Slaby a2e715
 		 */
Jiri Slaby a2e715
-		if (!boot_cpu_has(feature) == !(a->cpuid & ALTINSTR_FLAG_INV))
Jiri Slaby a2e715
-			goto next;
Jiri Slaby a2e715
+		if (!boot_cpu_has(feature) == !(a->cpuid & ALTINSTR_FLAG_INV)) {
Jiri Slaby a2e715
+			optimize_nops(instr, a->instrlen);
Jiri Slaby a2e715
+			continue;
Jiri Slaby a2e715
+		}
Jiri Slaby a2e715
 
Jiri Slaby a2e715
 		DPRINTK(ALT, "feat: %s%d*32+%d, old: (%pS (%px) len: %d), repl: (%px, len: %d)",
Jiri Slaby a2e715
 			(a->cpuid & ALTINSTR_FLAG_INV) ? "!" : "",
Jiri Slaby a2e715
@@ -316,37 +386,19 @@ void __init_or_module noinline apply_alt
Jiri Slaby a2e715
 			instr, instr, a->instrlen,
Jiri Slaby a2e715
 			replacement, a->replacementlen);
Jiri Slaby a2e715
 
Jiri Slaby a2e715
-		DUMP_BYTES(ALT, instr, a->instrlen, "%px:   old_insn: ", instr);
Jiri Slaby a2e715
-		DUMP_BYTES(ALT, replacement, a->replacementlen, "%px:   rpl_insn: ", replacement);
Jiri Slaby a2e715
-
Jiri Slaby a2e715
 		memcpy(insn_buff, replacement, a->replacementlen);
Jiri Slaby a2e715
 		insn_buff_sz = a->replacementlen;
Jiri Slaby a2e715
 
Jiri Slaby a2e715
-		/*
Jiri Slaby a2e715
-		 * 0xe8 is a relative jump; fix the offset.
Jiri Slaby a2e715
-		 *
Jiri Slaby a2e715
-		 * Instruction length is checked before the opcode to avoid
Jiri Slaby a2e715
-		 * accessing uninitialized bytes for zero-length replacements.
Jiri Slaby a2e715
-		 */
Jiri Slaby a2e715
-		if (a->replacementlen == 5 && *insn_buff == 0xe8) {
Jiri Slaby a2e715
-			*(s32 *)(insn_buff + 1) += replacement - instr;
Jiri Slaby a2e715
-			DPRINTK(ALT, "Fix CALL offset: 0x%x, CALL 0x%lx",
Jiri Slaby a2e715
-				*(s32 *)(insn_buff + 1),
Jiri Slaby a2e715
-				(unsigned long)instr + *(s32 *)(insn_buff + 1) + 5);
Jiri Slaby a2e715
-		}
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-		if (a->replacementlen && is_jmp(replacement[0]))
Jiri Slaby a2e715
-			recompute_jump(a, instr, replacement, insn_buff);
Jiri Slaby a2e715
-
Jiri Slaby a2e715
 		for (; insn_buff_sz < a->instrlen; insn_buff_sz++)
Jiri Slaby a2e715
 			insn_buff[insn_buff_sz] = 0x90;
Jiri Slaby a2e715
 
Jiri Slaby a2e715
+		apply_relocation(insn_buff, a->instrlen, instr, replacement, a->replacementlen);
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+		DUMP_BYTES(ALT, instr, a->instrlen, "%px:   old_insn: ", instr);
Jiri Slaby a2e715
+		DUMP_BYTES(ALT, replacement, a->replacementlen, "%px:   rpl_insn: ", replacement);
Jiri Slaby a2e715
 		DUMP_BYTES(ALT, insn_buff, insn_buff_sz, "%px: final_insn: ", instr);
Jiri Slaby a2e715
 
Jiri Slaby a2e715
 		text_poke_early(instr, insn_buff, insn_buff_sz);
Jiri Slaby a2e715
-
Jiri Slaby a2e715
-next:
Jiri Slaby a2e715
-		optimize_nops(instr, a->instrlen);
Jiri Slaby a2e715
 	}
Jiri Slaby a2e715
 }
Jiri Slaby a2e715
 
Jiri Slaby a2e715
@@ -851,6 +903,35 @@ static void __init int3_selftest(void)
Jiri Slaby a2e715
 	unregister_die_notifier(&int3_exception_nb);
Jiri Slaby a2e715
 }
Jiri Slaby a2e715
 
Jiri Slaby a2e715
+static __initdata int __alt_reloc_selftest_addr;
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+__visible noinline void __init __alt_reloc_selftest(void *arg)
Jiri Slaby a2e715
+{
Jiri Slaby a2e715
+	WARN_ON(arg != &__alt_reloc_selftest_addr);
Jiri Slaby a2e715
+}
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+static noinline void __init alt_reloc_selftest(void)
Jiri Slaby a2e715
+{
Jiri Slaby a2e715
+	/*
Jiri Slaby a2e715
+	 * Tests apply_relocation().
Jiri Slaby a2e715
+	 *
Jiri Slaby a2e715
+	 * This has a relative immediate (CALL) in a place other than the first
Jiri Slaby a2e715
+	 * instruction and additionally on x86_64 we get a RIP-relative LEA:
Jiri Slaby a2e715
+	 *
Jiri Slaby a2e715
+	 *   lea    0x0(%rip),%rdi  # 5d0: R_X86_64_PC32    .init.data+0x5566c
Jiri Slaby a2e715
+	 *   call   +0              # 5d5: R_X86_64_PLT32   __alt_reloc_selftest-0x4
Jiri Slaby a2e715
+	 *
Jiri Slaby a2e715
+	 * Getting this wrong will either crash and burn or tickle the WARN
Jiri Slaby a2e715
+	 * above.
Jiri Slaby a2e715
+	 */
Jiri Slaby a2e715
+	asm_inline volatile (
Jiri Slaby a2e715
+		ALTERNATIVE("", "lea %[mem], %%" _ASM_ARG1 "; call __alt_reloc_selftest;", X86_FEATURE_ALWAYS)
Jiri Slaby a2e715
+		: /* output */
Jiri Slaby a2e715
+		: [mem] "m" (__alt_reloc_selftest_addr)
Jiri Slaby a2e715
+		: _ASM_ARG1
Jiri Slaby a2e715
+	);
Jiri Slaby a2e715
+}
Jiri Slaby a2e715
+
Jiri Slaby a2e715
 void __init alternative_instructions(void)
Jiri Slaby a2e715
 {
Jiri Slaby a2e715
 	int3_selftest();
Jiri Slaby a2e715
@@ -927,6 +1008,8 @@ void __init alternative_instructions(voi
Jiri Slaby a2e715
 
Jiri Slaby a2e715
 	restart_nmi();
Jiri Slaby a2e715
 	alternatives_patched = 1;
Jiri Slaby a2e715
+
Jiri Slaby a2e715
+	alt_reloc_selftest();
Jiri Slaby a2e715
 }
Jiri Slaby a2e715
 
Jiri Slaby a2e715
 /**
Jiri Slaby a2e715
--- a/tools/objtool/arch/x86/special.c
Jiri Slaby a2e715
+++ b/tools/objtool/arch/x86/special.c
Jiri Slaby a2e715
@@ -42,13 +42,7 @@ bool arch_support_alt_relocation(struct
Jiri Slaby a2e715
 				 struct instruction *insn,
Jiri Slaby a2e715
 				 struct reloc *reloc)
Jiri Slaby a2e715
 {
Jiri Slaby a2e715
-	/*
Jiri Slaby a2e715
-	 * The x86 alternatives code adjusts the offsets only when it
Jiri Slaby a2e715
-	 * encounters a branch instruction at the very beginning of the
Jiri Slaby a2e715
-	 * replacement group.
Jiri Slaby a2e715
-	 */
Jiri Slaby a2e715
-	return insn->offset == special_alt->new_off &&
Jiri Slaby a2e715
-	       (insn->type == INSN_CALL || is_jump(insn));
Jiri Slaby a2e715
+	return true;
Jiri Slaby a2e715
 }
Jiri Slaby a2e715
 
Jiri Slaby a2e715
 /*