From 78d6d4b918068775c017808b8765561e9216ef34 Mon Sep 17 00:00:00 2001
From: Chuan Zheng <zhengchuan@huawei.com>
Date: Tue, 15 Dec 2020 20:00:19 +0800
Subject: [PATCH] tls: add support for TLS symbol with IE model

Since we have implement init-exec mode for qemu, we can take care
of tls symbol in libcare.
i.  as for x86, we do need to do relocation for both global and local
    tls symbol. This is because it uses %fs register to restore GOT
    offset.
ii. it is really tricky for global tls symbol reference on arch64,
    because it uses adrp and ldr to locate the offset of GOT entry.
    We need to parse the GOT offset in fix-relocation stage and get
    the right address. No more relocation for local symbol because it
    has been optimized as relative addressing other than GOT reference.

Change-Id: If21e2e7726dc9798ecee693fb3be558c1fd2f17d
Signed-off-by: imxcc <xingchaochao@huawei.com>
---
 libcare.spec                  |  5 +-
 src/Makefile                  |  2 +-
 src/arch/aarch64/arch_elf.c   | 23 +++++++++
 src/arch/aarch64/arch_strip.c | 95 ++++++++++++++++++++++++++++++++++
 src/arch/x86/arch_strip.c     | 96 +++++++++++++++++++++++++++++++++++
 src/include/kpatch_strip.h    | 18 +++++++
 src/kpatch_strip.c            |  6 ++-
 7 files changed, 241 insertions(+), 4 deletions(-)
 create mode 100644 src/arch/aarch64/arch_strip.c
 create mode 100644 src/arch/x86/arch_strip.c
 create mode 100644 src/include/kpatch_strip.h

diff --git a/libcare.spec b/libcare.spec
index 5e7e5c5..275de8b 100644
--- a/libcare.spec
+++ b/libcare.spec
@@ -1,7 +1,7 @@
 Version: 0.1.4
 Name: libcare
 Summary: LibCare tools
-Release: 14%{?dist}
+Release: 15%{?dist}
 Group: Applications/System
 License: GPLv2
 Url: http://www.cloudlinux.com
@@ -158,6 +158,9 @@ exit 0
 %endif
 
 %changelog
+* Thu Sep 23 2021 Chuan Zheng <zhengchuan@huawei.com> - 0.1.4-15
+- tls: add support for tls symbol
+
 * Thu Sep 23 2021 Jiajie Li <lijiajie11@huawei.com> - 0.1.4-14
 - arch/aarch64/arch_parse: improve VAR_CBLOCK start indentify
 
diff --git a/src/Makefile b/src/Makefile
index 0571400..f88f454 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -55,7 +55,7 @@ libcare-stresstest: LDLIBS += -lelf -lrt $(LIBUNWIND_LIBS)
 
 libcare-client: libcare-client.o
 
-kpatch_strip: kpatch_strip.o kpatch_elf_objinfo.o kpatch_log.o
+kpatch_strip: kpatch_strip.o kpatch_elf_objinfo.o kpatch_log.o arch_strip.o
 kpatch_strip: LDLIBS = -lelf
 
 libcare-cc: kpatch_cc.o
diff --git a/src/arch/aarch64/arch_elf.c b/src/arch/aarch64/arch_elf.c
index f92dc93..735a3d1 100644
--- a/src/arch/aarch64/arch_elf.c
+++ b/src/arch/aarch64/arch_elf.c
@@ -24,6 +24,18 @@ static int kpatch_arch_apply_relocate(GElf_Rela *r, GElf_Sym *s,
 	uint32_t immHi;
 
 	switch (GELF_R_TYPE(r->r_info)) {
+	case R_AARCH64_NONE:
+	case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
+		break;
+	case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
+		kpdebug("R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: val=0x%lx\n", val);
+		val = (val >> 12) - ((unsigned long)loc2 >> 12);
+		immLo = (val & 0x3) << 29;
+		immHi = (val & 0x1FFFFC) << 3;
+		mask = (0x3 << 29) | (0x1FFFFC << 3);
+		*(unsigned int*)loc = (*(unsigned int*)loc & ~mask) | immLo | immHi;
+		kpdebug("R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: loc=0x%x, val=0x%lx\n", *(unsigned int *)loc, val);
+		break;
 	case R_AARCH64_ABS64:
 		*(unsigned long *)loc = val;
 		kpdebug("R_AARCH64_ABS64: loc=0x%x, val =0x%lx\n",*(unsigned int*)loc,val);
@@ -155,6 +167,17 @@ int kpatch_arch_apply_relocate_add(struct object_file *o, GElf_Shdr *relsec)
 		if (is_kpatch_info && is_undef_symbol(s)) {
 			val = s->st_size;
 		}
+		/* 
+		   Special care for TLS symbol
+		   i.  For GLOBAL TLS symbol, point to GOT entry
+		   ii. For LOCAL TLS symbol, do nothing since everything is done in static-link
+		 */
+		if (GELF_ST_TYPE(s->st_info) == STT_TLS) {
+			if (GELF_ST_BIND(s->st_info) == STB_LOCAL)
+				return 0;
+			else
+				val = r->r_addend + o->load_offset;
+		}
 
 		if(kpatch_arch_apply_relocate(r, s, loc, loc2, val)) {
 			return -1;
diff --git a/src/arch/aarch64/arch_strip.c b/src/arch/aarch64/arch_strip.c
new file mode 100644
index 0000000..67f52a5
--- /dev/null
+++ b/src/arch/aarch64/arch_strip.c
@@ -0,0 +1,95 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <getopt.h>
+#include "include/kpatch_file.h"
+#include "include/kpatch_common.h"
+
+#include <gelf.h>
+#include "include/kpatch_elf_objinfo.h"
+
+#include "include/kpatch_log.h"
+#include "include/kpatch_strip.h"
+
+
+/* Update relocation against TLS symbol.
+ *
+ * Thread-Local Storage variables require special care because they are
+ * referenced by the offset into Thread Local Storage allocated for each
+ * thread. There are different models for TLS and we only support Initial-Exec.
+ *
+ * Returns 1 when symbol must be updated, 0 when everything is OK, -1 on error.
+ */
+
+#define MASK(n) ((1u << (n)) - 1)
+
+static uint32_t objinfo_find_tls_got_by_offset(GElf_Rela *rel,
+						GElf_Shdr *sh_text,
+						unsigned char *text)
+{
+	int i;
+	unsigned long off;
+	uint32_t insn;
+	uint32_t imm;
+	uint32_t gotoffset;
+
+	off = rel->r_offset - sh_text->sh_addr;
+
+	/* Get ins from text according to ARM ABI */
+	insn = (unsigned long)text[off];
+	for (i = 1; i <= 3; i++) {
+		insn = insn + ((unsigned long)text[off + i] << (8 * i));
+	}
+
+	imm = (((insn >> 5) & MASK (19)) << 2) | ((insn >> 29) & MASK (2));
+	imm = imm << 12;
+
+	gotoffset = imm + (sh_text->sh_addr & 0xfffff000);
+
+	return gotoffset;
+}
+
+int
+kpatch_arch_fixup_rela_update_tls(kpatch_objinfo *origbin,
+			     kpatch_objinfo *patch,
+			     GElf_Rela *rela,
+			     GElf_Sym *sym,
+			     GElf_Shdr *sh_text,
+			     unsigned char *text)
+{
+	unsigned long got_offset;
+
+	char *symname, *tmp;
+
+	/* Do nothing for Local TLS symbol */
+	if (GELF_ST_BIND(sym->st_info) != STB_LOCAL)
+		return 0;
+
+	symname = (char *)kpatch_objinfo_strptr(patch,
+					SYMBOL_NAME, sym->st_name);
+	tmp = strchr(symname, '@');
+	if (tmp != NULL)
+		*tmp = '\0';
+
+	switch (GELF_R_TYPE(rela->r_info)) {
+	case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
+		got_offset = objinfo_find_tls_got_by_offset(rela, sh_text, text);
+		kpinfo("Fix relocation for TLS symbol %s from %lx to %lx\n",
+			symname, rela->r_addend, got_offset);
+		rela->r_addend = got_offset;
+		break;
+	case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
+		break;
+	default:
+		kpfatalerror("non-supported TLS model\n");
+		return -1;
+	}
+
+	return 0;
+}
+
diff --git a/src/arch/x86/arch_strip.c b/src/arch/x86/arch_strip.c
new file mode 100644
index 0000000..4e0b4ca
--- /dev/null
+++ b/src/arch/x86/arch_strip.c
@@ -0,0 +1,96 @@
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <getopt.h>
+#include "include/kpatch_file.h"
+#include "include/kpatch_common.h"
+
+#include <gelf.h>
+#include "include/kpatch_elf_objinfo.h"
+
+#include "include/kpatch_log.h"
+#include "include/kpatch_strip.h"
+
+#define SECTION_OFFSET_FOUND        0x0
+#define SECTION_NOT_FOUND       0x1
+
+/* Update relocation against TLS symbol.
+ *
+ * Thread-Local Storage variables require special care because they are
+ * referenced by the offset into Thread Local Storage allocated for each
+ * thread. There are different models for TLS and we only support Initial-Exec.
+ *
+ * The following types of relocations are handled:
+ *
+ * - Relocs of type TPOFF32 targeting symbols in the original are changed
+ *   to reloc type NONE after the symbol is checked to be present at the
+ *   same place in the original binary. (TODO)
+ *
+ *   *NOTE* the only way to support new variables allocated by patch is to use
+ *   dlopen-loaded patches.
+ * - Relocs of type TPOFF64 are ignored. These are only used for long memory
+ *   model or as entries to GOT.
+ * - Relocs of type GOTTPOFF are quite tricky. These usually point to a GOT
+ *   entry filled with TPOFF64 relocation. We can't do this relocation on our
+ *   own because it requires digging into glibc internels with a hack.
+ *
+ *   Instead, we cheat here and find the appropriate TPOFF64 relocations IN
+ *   THE ORIGINAL object and make GOTTPOFF point there. This is different for
+ *   local/global symbols but is not very hard.
+ *
+ * Returns 1 when symbol must be updated, 0 when everything is OK, -1 on error.
+ */
+int
+kpatch_arch_fixup_rela_update_tls(kpatch_objinfo *origbin,
+			     kpatch_objinfo *patch,
+			     GElf_Rela *rela,
+			     GElf_Sym *sym,
+			     GElf_Shdr *sh_text,
+			     unsigned char *text)
+{
+	switch (GELF_R_TYPE(rela->r_info)) {
+	case R_X86_64_GOTTPOFF:
+	case R_X86_64_TPOFF32: {
+		const char *symname;
+		int rv;
+		unsigned long off;
+		/* Leave the value as is, just check offset is the same. */
+		rela->r_info = GELF_R_INFO(0, R_X86_64_NONE);
+		symname = kpatch_objinfo_strptr(patch, SYMBOL_NAME,
+						sym->st_name);
+
+		rv = kpatch_get_original_symbol_loc(
+			origbin, symname, &off, NULL);
+		if (rv == SECTION_NOT_FOUND) {
+			kpfatalerror(
+				"TLS symbol %s not found in original binary",
+				symname);
+		}
+
+	    kpinfo("Compare TLS symbol %s offset: %lx in origbinal binary, %lx in patch\n",
+				symname, off, sym->st_value);
+        if (off != sym->st_value) {
+                kpfatalerror("TLS symbol %s has different offset!\n", symname);
+        }
+
+		return 0;
+	}
+
+	case R_X86_64_DTPOFF32:
+	case R_X86_64_DTPOFF64:
+	case R_X86_64_DTPMOD64:
+	case R_X86_64_TLSGD:
+	case R_X86_64_TLSLD:
+	case R_X86_64_TPOFF64:
+	default:
+		kpfatalerror("non-supported TLS model\n");
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/src/include/kpatch_strip.h b/src/include/kpatch_strip.h
new file mode 100644
index 0000000..4a56c8b
--- /dev/null
+++ b/src/include/kpatch_strip.h
@@ -0,0 +1,18 @@
+#ifndef __KPATCH_STRIP_H__
+#define __KPATCH_STRIP_H__
+
+int
+kpatch_get_original_symbol_loc(kpatch_objinfo *origbin,
+                   const char *symname,
+                   size_t *symoff,
+                   const char **secname);
+
+int
+kpatch_arch_fixup_rela_update_tls(kpatch_objinfo *origbin,
+		kpatch_objinfo *patch,
+		GElf_Rela *rela,
+		GElf_Sym *sym,
+		GElf_Shdr *sh_text,
+		unsigned char *text);
+
+#endif
diff --git a/src/kpatch_strip.c b/src/kpatch_strip.c
index 6ad56a3..26dcd58 100644
--- a/src/kpatch_strip.c
+++ b/src/kpatch_strip.c
@@ -14,6 +14,7 @@
 #include "include/kpatch_elf_objinfo.h"
 
 #include "include/kpatch_log.h"
+#include "include/kpatch_strip.h"
 
 #define ALIGN(off,sz) (((off)+(sz)-1)&~((sz)-1))
 
@@ -211,7 +212,7 @@ kpatch_get_symbol_offset_rel_section(kpatch_objinfo *oi,
 	return 0;
 }
 
-static int
+int
 kpatch_get_original_symbol_loc(kpatch_objinfo *origbin,
 			       const char *symname,
 			       size_t *symoff,
@@ -499,7 +500,8 @@ kpatch_fixup_rela_one(kpatch_objinfo *origbin,
 	}
 
 	if (GELF_ST_TYPE(sym->st_info) == STT_TLS) {
-		rv = kpatch_fixup_rela_update_tls(origbin, patch, rel, sym);
+		rv = kpatch_arch_fixup_rela_update_tls(origbin, patch, rel,
+							sym, sh_text, text);
 		if (rv < 0)
 			kpfatalerror("kpatch_fixup_rela_update_tls");
 
-- 
2.19.1

