Commit e0746bde authored by Fangrui Song's avatar Fangrui Song Committed by Shuah Khan
Browse files

selftests/vDSO: support DT_GNU_HASH

glibc added support for DT_GNU_HASH in 2006 and DT_HASH has been
obsoleted for more than one decade in many Linux distributions.

Many vDSOs support DT_GNU_HASH. This patch adds selftests support.

Link: https://lore.kernel.org/r/20241206130724.7944-2-xry111@xry111.site


Signed-off-by: default avatarFangrui Song <i@maskray.me>
Tested-by: default avatarXi Ruoyao <xry111@xry111.site>
Signed-off-by: Xi Ruoyao <xry111@xry111.site> # rebase
Signed-off-by: default avatarShuah Khan <skhan@linuxfoundation.org>
parent eed8ecdf
Loading
Loading
Loading
Loading
+82 −28
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ static struct vdso_info
	/* Symbol table */
	ELF(Sym) *symtab;
	const char *symstrings;
	ELF(Word) *gnu_hash;
	ELF_HASH_ENTRY *bucket, *chain;
	ELF_HASH_ENTRY nbucket, nchain;

@@ -81,6 +82,16 @@ static unsigned long elf_hash(const char *name)
	return h;
}

static uint32_t gnu_hash(const char *name)
{
	const unsigned char *s = (void *)name;
	uint32_t h = 5381;

	for (; *s; s++)
		h += h * 32 + *s;
	return h;
}

void vdso_init_from_sysinfo_ehdr(uintptr_t base)
{
	size_t i;
@@ -123,6 +134,7 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
	 */
	ELF_HASH_ENTRY *hash = 0;
	vdso_info.symstrings = 0;
	vdso_info.gnu_hash = 0;
	vdso_info.symtab = 0;
	vdso_info.versym = 0;
	vdso_info.verdef = 0;
@@ -143,6 +155,11 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
				((uintptr_t)dyn[i].d_un.d_ptr
				 + vdso_info.load_offset);
			break;
		case DT_GNU_HASH:
			vdso_info.gnu_hash =
				(ELF(Word) *)((uintptr_t)dyn[i].d_un.d_ptr +
					      vdso_info.load_offset);
			break;
		case DT_VERSYM:
			vdso_info.versym = (ELF(Versym) *)
				((uintptr_t)dyn[i].d_un.d_ptr
@@ -155,17 +172,27 @@ void vdso_init_from_sysinfo_ehdr(uintptr_t base)
			break;
		}
	}
	if (!vdso_info.symstrings || !vdso_info.symtab || !hash)
	if (!vdso_info.symstrings || !vdso_info.symtab ||
	    (!hash && !vdso_info.gnu_hash))
		return;  /* Failed */

	if (!vdso_info.verdef)
		vdso_info.versym = 0;

	/* Parse the hash table header. */
	if (vdso_info.gnu_hash) {
		vdso_info.nbucket = vdso_info.gnu_hash[0];
		/* The bucket array is located after the header (4 uint32) and the bloom
		 * filter (size_t array of gnu_hash[2] elements).
		 */
		vdso_info.bucket = vdso_info.gnu_hash + 4 +
				   sizeof(size_t) / 4 * vdso_info.gnu_hash[2];
	} else {
		vdso_info.nbucket = hash[0];
		vdso_info.nchain = hash[1];
		vdso_info.bucket = &hash[2];
		vdso_info.chain = &hash[vdso_info.nbucket + 2];
	}

	/* That's all we need. */
	vdso_info.valid = true;
@@ -209,36 +236,63 @@ static bool vdso_match_version(ELF(Versym) ver,
		&& !strcmp(name, vdso_info.symstrings + aux->vda_name);
}

void *vdso_sym(const char *version, const char *name)
static bool check_sym(ELF(Sym) *sym, ELF(Word) i, const char *name,
		      const char *version, unsigned long ver_hash)
{
	unsigned long ver_hash;
	if (!vdso_info.valid)
		return 0;

	ver_hash = elf_hash(version);
	ELF(Word) chain = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket];

	for (; chain != STN_UNDEF; chain = vdso_info.chain[chain]) {
		ELF(Sym) *sym = &vdso_info.symtab[chain];

	/* Check for a defined global or weak function w/ right name. */
	if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC)
			continue;
		return false;
	if (ELF64_ST_BIND(sym->st_info) != STB_GLOBAL &&
	    ELF64_ST_BIND(sym->st_info) != STB_WEAK)
			continue;
		if (sym->st_shndx == SHN_UNDEF)
			continue;
		return false;
	if (strcmp(name, vdso_info.symstrings + sym->st_name))
			continue;
		return false;

	/* Check symbol version. */
		if (vdso_info.versym
		    && !vdso_match_version(vdso_info.versym[chain],
					   version, ver_hash))
			continue;
	if (vdso_info.versym &&
	    !vdso_match_version(vdso_info.versym[i], version, ver_hash))
		return false;

		return (void *)(vdso_info.load_offset + sym->st_value);
	return true;
}

void *vdso_sym(const char *version, const char *name)
{
	unsigned long ver_hash;
	if (!vdso_info.valid)
		return 0;

	ver_hash = elf_hash(version);
	ELF(Word) i;

	if (vdso_info.gnu_hash) {
		uint32_t h1 = gnu_hash(name), h2, *hashval;

		i = vdso_info.bucket[h1 % vdso_info.nbucket];
		if (i == 0)
			return 0;
		h1 |= 1;
		hashval = vdso_info.bucket + vdso_info.nbucket +
			  (i - vdso_info.gnu_hash[1]);
		for (;; i++) {
			ELF(Sym) *sym = &vdso_info.symtab[i];
			h2 = *hashval++;
			if (h1 == (h2 | 1) &&
			    check_sym(sym, i, name, version, ver_hash))
				return (void *)(vdso_info.load_offset +
						sym->st_value);
			if (h2 & 1)
				break;
		}
	} else {
		i = vdso_info.bucket[elf_hash(name) % vdso_info.nbucket];
		for (; i; i = vdso_info.chain[i]) {
			ELF(Sym) *sym = &vdso_info.symtab[i];
			if (sym->st_shndx != SHN_UNDEF &&
			    check_sym(sym, i, name, version, ver_hash))
				return (void *)(vdso_info.load_offset +
						sym->st_value);
		}
	}

	return 0;